JavaScript核心问题与解答详解

内容分享7小时前发布
0 0 0

table {
border-collapse: collapse;
width: 100%;
margin-bottom: 1rem;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
pre {
background-color: #f8f8f8;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
}

1、在控制台中执行以下每行代码的结果是什么?为什么?var a; typeof a; var s = ‘1s’; s++; !!’false’; !!undefined; typeof -Infinity; 10 % “0”; undefined == null; false === “”; typeof “2E+2”; a = 3e+3; a++;

var a; typeof a;

:结果是 “undefined”。

因为声明变量但未初始化时,JavaScript 自动将其初始化为

undefined


typeof

对其操作返回 “undefined”。

var s = '1s'; s++;

:结果是

NaN



将字符串

'1s'

进行自增操作,无法转换为有效数字,运算结果为

NaN

!!'false';

:结果是

true



先将字符串

'false'

转换为布尔值,非空字符串为

true

,再取反两次仍为

true

!!undefined;

:结果是

false



undefined

转换为布尔值是

false

,取反两次后为

false

typeof -Infinity;

:结果是 “number”。

-Infinity

是数字类型的特殊值,

typeof

对其操作返回 “number”。

10 % "0";

:结果是

NaN



对字符串

"0"

进行取模运算,会尝试将其转换为数字

0

,10 对

0

取模结果是

NaN

undefined == null;

:结果是

true



在 JavaScript 中,

undefined


null

使用

==

比较时结果为

true

false === "";

:结果是

false



false

是布尔类型,

""

是字符串类型,使用

===

进行严格比较,类型不同结果为

false

typeof "2E+2";

:结果是 “string”。

"2E+2"

是字符串,

typeof

对其操作返回 “string”。

a = 3e+3; a++;

:结果是

3001



3e+3

表示

3000

,赋值给

a

后自增

1

变为

3001

2、执行

var v = v || 10;

后,v 的值是多少?分别尝试先将 v 设置为 100、0 或 null。


v

先设为 100 时,执行

var v = v || 10;


v

为 100;


v

先设为 0 时,执行后

v

为 10;


v

先设为

null

时,执行后

v

为 10。

因为

0


null

属于假值(falsy),逻辑或运算会返回第二个值

10

,而

100

为真值(truthy),会返回

100

3、编写一个小程序,打印出乘法表。提示:使用一个循环嵌套在另一个循环中。

以下是使用 JavaScript 编写的打印乘法表的程序示例:


for (var i = 1; i <= 9; i++) {
  var row = '';
  for (var j = 1; j <= i; j++) {
    row += j + '×' + i + '=' + (i * j) + ' ';
  }
  console.log(row);
}

这段代码使用了两层嵌套的

for

循环,外层循环控制行数,内层循环控制每行的列数,最终打印出乘法表。

4、以下每行代码在控制台中分别打印出什么内容?> parseInt(1e1); > parseInt(‘1e1’); > parseFloat(‘1e1’); > isFinite(0/10); > isFinite(20/0); > isNaN(parseInt(NaN));

10、1、10、true、false、true

5、以下代码会弹出什么内容?var a = 1; function f() { function n() { alert(a); } var a = 2; n(); } f();

由于变量提升,函数

n

中访问的

a

是函数

f

中的局部变量

a

,在

alert


a

已声明但未赋值,所以会弹出

undefined

6、以下示例都会弹出“Boo!”,请解释原因。示例1:var f = alert; eval(‘f(“Boo!”)’); 示例2:var e; var f = alert; eval(‘e=f’)(‘Boo!’); 示例3:(function(){ return alert;} )()(‘Boo!’);

在这三个示例中,最终都是调用了

alert

函数并传入了字符串

Boo!

作为参数。

示例1中,将

alert

函数赋值给变量

f

,然后使用

eval

执行

f('Boo!')

,即调用

f

也就是

alert

函数弹出

Boo!

示例2里,先把

alert

赋值给

f


eval('e=f')


f

的值(即

alert

函数)赋给

e

,再调用

e('Boo!')

,同样是调用

alert

弹出

Boo!

示例3中,立即执行函数返回

alert

函数,接着调用返回的

alert

函数并传入

Boo!

,所以也会弹出

Boo!

7、看这段代码:function F() { function C() { return this; } return C(); } var o = new F(); 这里的函数 C 内部的 this 值是指向全局对象还是对象 o?


this 值指向全局对象。因为函数 C 是在全局作用域中被调用的,而非作为对象的方法调用,也没有使用 `call`、`apply` 或 `bind` 改变 this 指向,所以 this 指向全局对象。

8、执行以下代码片段的结果是什么?function C(){ this.a = 1; return false;}console.log(typeof new C());

输出结果为

object

。因为构造函数中返回非对象值,会按常规行为返回

this


new C()

创建的是对象。

9、执行以下代码片段的结果是什么?


let c = [1, 2, [1, 2]];
c.sort();
c.join('--');
console.log(c);

代码先对数组

c

进行排序,由于默认排序是按字符排序,所以排序后为

[1, [1, 2], 2]



c.join('--')

虽将数组元素用

--

连接成字符串,但不改变原数组。

最后

console.log(c)

输出排序后的数组

[1, [1, 2], 2]

10、更新你的MyString()构造函数,使其包含一个reverse()方法。尝试利用数组有reverse()方法这一特性。


MyString

构造函数的原型上添加

reverse()

方法,示例代码如下:


function MyString(str) {
  this.value = str;
}

MyString.prototype.reverse = function () {
  return this.value.split('').reverse().join('');
};

11、假设 Math 对象不存在。创建一个 MyMath 对象,它还提供以下额外方法:MyMath.rand(min, max, inclusive) — 如果 inclusive 为 true(默认值),则生成一个介于 min 和 max 之间(包含两端)的随机数;MyMath.min(array) — 返回给定数组中的最小数字;MyMath.max(array) — 返回给定数组中的最大数字。

以下是实现该 MyMath 对象的 JavaScript 代码:


const MyMath = {
  rand: function(min, max, inclusive = true) {
    if (inclusive) {
      return Math.floor(Math.random() * (max - min + 1)) + min;
    }
    return Math.random() * (max - min) + min;
  },
  min: function(array) {
    return Math.min(...array);
  },
  max: function(array) {
    return Math.max(...array);
  }
};

以上代码实现了 MyMath 对象及其所需的方法。

12、创建一个名为shape的对象,该对象具有type属性和getType()方法。

以下是创建该对象的代码:


var shape = {
  type: '特定类型',
  getType: function() {
    return this.type;
  }
};

这里的

'特定类型'

可替换为你想要的具体类型。

13、定义一个

Triangle()

构造函数,其原型为

shape

。使用

Triangle()

创建的对象应具有三个自有属性

a


b


c

,分别代表三角形三条边的长度。

以下是符合要求的代码实现:


var shape = { 
    name: 'Shape', 
    toString: function () { 
        return this.name; 
    } 
};

function Triangle(a, b, c) { 
    this.a = a; 
    this.b = b; 
    this.c = c; 
}

Triangle.prototype = shape;

14、向原型添加一个名为getPerimeter()的新方法。假设已有Triangle()构造函数,用于创建代表三角形的对象,该对象有a、b、c三个属性代表三角形三边长度,通过此方法可以计算并返回三角形的周长。

可按如下方式向

Triangle

构造函数的原型添加

getPerimeter()

方法:


Triangle.prototype.getPerimeter = function() {
  return this.a + this.b + this.c;
};

此方法返回三角形的周长。

15、遍历对象 t,仅显示其自身的属性和方法(不包括原型的属性和方法)。

可以使用

hasOwnProperty()

方法来实现。示例代码如下:


for (var prop in t) {
  if (t.hasOwnProperty(prop)) {
    console.log(prop + '=' + t[prop]);
  }
}

16、让代码 [1, 2, 3, 4, 5, 6, 7, 8, 9].shuffle(); 正常工作

要让这段代码正常工作,需要为数组原型添加一个

shuffle

方法。以下是实现该方法的示例代码:


Array.prototype.shuffle = function() {
  let currentIndex = this.length, randomIndex;
  // 当还有元素未被处理时
  while (currentIndex != 0) {
    // 随机选择一个剩余的元素
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex--;
    // 交换当前元素和随机选择的元素
    [this[currentIndex], this[randomIndex]] = [this[randomIndex], this[currentIndex]];
  }
  return this;
};

let result = [1, 2, 3, 4, 5, 6, 7, 8, 9].shuffle();
console.log(result);

上述代码通过 Fisher-Yates 洗牌算法为数组原型添加了

shuffle

方法,实现了数组元素的随机打乱。

17、利用画布进行练习,尝试在画布上绘制几个三角形、正方形和长方形。

以下是示例代码,用于在画布上绘制三角形、正方形和长方形:


// 定义三角形的三个点
var p1 = new Point(100, 100);
var p2 = new Point(300, 100);
var p3 = new Point(200, 0);

// 创建三角形实例并绘制
var t = new Triangle(p1, p2, p3);
t.draw();

// 创建长方形实例并绘制
var r = new Rectangle(new Point(200, 200), 50, 100);
r.draw();

// 创建正方形实例并绘制
var s = new Square(new Point(130, 130), 50);
s.draw();

18、使用画布示例进行练习。尝试不同的操作,例如:为更多形状添加构造函数,如梯形、菱形、风筝形和五边形。如果你想更多地了解画布标签,也创建一个圆形构造函数。它需要重写父类的 draw() 方法。

可以在原有的形状构造函数基础上,为梯形、菱形、风筝形、五边形添加构造函数。若要创建圆形构造函数,需重写父类的

draw()

方法。

以 JavaScript 为例,以下是简单的实现思路:

首先定义父类

Shape

及其

draw()

方法

然后创建圆形构造函数

Circle

并覆盖

draw()

方法

代码示例如下:


function Shape() {
    this.draw = function() {
        console.log('Drawing a shape');
    }
}

function Circle() {
    this.draw = function() {
        console.log('Drawing a circle');
    }
}

对于梯形、菱形、风筝形和五边形,也可类似地创建构造函数,根据各自的特点实现相应逻辑。

19、利用画布示例进行练习。尝试不同的操作,思考是否能想出另一种解决问题的方法并使用另一种继承类型。

可以考虑使用 ES6 中的类继承。ES6 引入了

class

关键字和

extends

语法,使继承的实现更加直观和简洁。示例代码如下:


class Shape {
    constructor() {
        this.name = 'Shape';
    }
    toString() {
        return this.name;
    }
}

class TwoDShape extends Shape {
    constructor() {
        super();
        this.name = '2D shape';
    }
}

class Triangle extends TwoDShape {
    constructor(side, height) {
        super();
        this.side = side;
        this.height = height;
        this.name = 'Triangle';
    }
    getArea() {
        return this.side * this.height / 2;
    }
}

此外,还可以使用组合继承,它结合了原型链继承和构造函数继承的优点,既能继承原型上的属性和方法,又能继承构造函数里的属性。

20、利用 JavaScript 进行练习。尝试不同的操作,例如:使用

uber

让子类访问其父类的方法。添加功能,使父类能够跟踪它们的子类,可使用一个包含子类数组的属性。


要实现让父类跟踪子类的功能,可在父类构造函数里添加一个数组属性来存储子类实例。以下是示例代码:

```javascript
function Shape() {
    this.children = [];
}

// augment prototype
Shape.prototype.name = 'Shape';
Shape.prototype.toString = function () {
    var const = this.constructor;
    return const.uber ? this.const.uber.toString() + ', ' + this.name : this.name;
};

function TwoDShape() {
    // 当创建 TwoDShape 实例时,将其添加到 Shape 的 children 数组中
    Shape.prototype.children.push(this);
}

// take care of inheritance
var F = function () {};
F.prototype = Shape.prototype;
TwoDShape.prototype = new F();
TwoDShape.prototype.constructor = TwoDShape;
TwoDShape.uber = Shape.prototype;

// augment prototype
TwoDShape.prototype.name = '2D shape';

function Triangle(side, height) {
    this.side = side;
    this.height = height;
    // 当创建 Triangle 实例时,将其添加到 TwoDShape 的 children 数组中
    TwoDShape.prototype.children.push(this);
}

// take care of inheritance
var F = function () {};
F.prototype = TwoDShape.prototype;
Triangle.prototype = new F();
Triangle.prototype.constructor = Triangle;
Triangle.uber = TwoDShape.prototype;

在上述代码中,

Shape

构造函数里添加了

children

数组,每次创建

TwoDShape

实例时,该实例会被添加到

Shape.prototype.children

数组;每次创建

Triangle

实例时,该实例会被添加到

TwoDShape.prototype.children

数组,这样父类就能跟踪子类了。



##21、作为一个浏览器对象模型(BOM)练习,尝试编写代码实现以下两个功能。功能一:打开一个200x200的弹出窗口,然后将其缓慢逐渐调整为400x400大小。接着,让窗口像发生地震一样移动。你只需要使用一个move*()函数,进行一次或多次setInterval()调用,可能还需要一次setTimeout()/clearInterval()调用以停止整个过程。功能二:将当前日期/时间显示在文档标题中,并像时钟一样每秒更新一次。
# 练习示例代码

## 第一个练习:模拟地震效果

你可以先使用 `window.open()` 打开一个 200x200 的窗口,通过多次调用 `window.resizeBy()` 缓慢调整窗口大小,使用 `window.moveBy()` 和 `setInterval()` 让窗口移动模拟地震,最后用 `setTimeout()` 和 `clearInterval()` 停止。

```javascript
// 打开200x200的窗口
var win = window.open('', '', 'width=200,height=200');

// 逐渐调整窗口大小
var resizeInterval = setInterval(function() {
    if (win.outerWidth < 400 || win.outerHeight < 400) {
        win.resizeBy(1, 1);
    } else {
        clearInterval(resizeInterval);

        // 开始移动窗口模拟地震
        var moveInterval = setInterval(function() {
            var x = Math.random() * 10 - 5;
            var y = Math.random() * 10 - 5;
            win.moveBy(x, y);
        }, 100);

        // 一段时间后停止移动
        setTimeout(function() {
            clearInterval(moveInterval);
        }, 5000);
    }
}, 100);

第二个练习:每秒更新页面标题

使用

setInterval()

每秒更新

document.title


setInterval(function() {
    var now = new Date();
    document.title = now.toLocaleString();
}, 1000);

22、以不同方式实现walkDOM()函数,并且让它接受一个回调函数,而非硬编码console.log()。

以下是实现代码:


function walkDOM(n, callback) {
    do {
        callback(n);
        if (n.hasChildNodes()) {
            walkDOM(n.firstChild, callback);
        }
    } while (n = n.nextSibling);
}

在这个新的

walkDOM

函数中,接受两个参数,

n

是要遍历的节点,

callback

是回调函数。在遍历过程中,使用传入的回调函数处理每个节点,而非硬编码的

console.log()

23、使用innerHTML移除内容很简单(document.body.innerHTML = ‘’),但并非总是最佳选择。当被移除的元素上附加了事件监听器时,问题就来了,在IE浏览器中这些事件监听器不会被移除,这会导致浏览器内存泄漏,因为它存储了对不存在事物的引用。实现一个通用函数,该函数在删除DOM节点之前先移除所有事件监听器。你可以遍历节点的属性并检查其值是否为函数。如果是,那么它很可能是像onclick这样的属性。在将元素从树中移除之前,你需要将其设置为null。

以下是实现该功能的示例代码:


function removeNodeWithListeners(node) {
    for (let i = 0; i < node.attributes.length; i++) {
        const attr = node.attributes[i];
        if (typeof node[attr.name] === 'function') {
            node[attr.name] = null;
        }
    }
    if (node.hasChildNodes()) {
        const children = Array.from(node.childNodes);
        for (let child of children) {
            removeNodeWithListeners(child);
        }
    }
    node.parentNode.removeChild(node);
}

// 使用示例
// 假设你有一个要移除的节点
const elementToRemove = document.getElementById('yourElementId');
removeNodeWithListeners(elementToRemove);

24、创建一个名为 include() 的函数,用于按需引入外部脚本。该函数需要动态创建一个新的

© 版权声明

相关文章

暂无评论

none
暂无评论...