JavaScript 易错知识点整理
# 变量作用域
var a = 1;
function test() {
var a = 2;
console.log(a); // 2
}
test();
2
3
4
5
6
上面的 test 函数作用域中声明并赋值了 a,且在 console 之上,所以遵循就近原则输出 a 等于 2。
var a = 1;
function test2() {
console.log(a); // undefined
var a = 2;
}
test2();
2
3
4
5
6
上方的函数作用域中虽然声明并赋值了 a,但位于 console 之下,a 变量被提升,输出时已声明但尚未被赋值,所以输出 undefined。
var a = 1;
function test3() {
console.log(a); // 1
a = 2;
}
test3();
2
3
4
5
6
上方的函数作用域中 a 被重新赋值,未被重新声明,且位于 console 之下,所以输出全局作用域中的 a。
let b = 1;
function test4() {
console.log(b); // b is not undefined
let b = 2;
}
test4();
2
3
4
5
6
上方的函数作用域中使用了 ES6 的 let 重新声明了变量 b,而 let 不同于 var 其不存在变量提升的功能,所以输出报错b is not undefined
function test5() {
let a = 1;
{
let a = 2;
}
console.log(a); // 1
}
test5();
2
3
4
5
6
7
8
上方的函数作用域中用 let 声明了 a 为 1,并在块级作用域中声明了 a 为 2,因为 console 并不在函数内的块级作用域中,所以输出 1。
# 类型比较
var arr = [],
arr2 = [1];
console.log(arr === arr2); // false
2
3
4
上方两个不同的数组比较,console 为 false。
var arr = [],
arr2 = [];
console.log(arr === arr2); // false
2
3
4
上方两个相同的数组比较,因为两个单独的数组永不相等,所以 console 为 false。
var arr = [],
obj = {};
console.log(typeof arr === typeof obj); // true
2
3
4
上方利用 typeof 比较数组和对象,因为 typeof 获取null
、数组
、对象
的类型都为object
,所以 console 为 true。
var arr = [];
console.log(arr instanceof Object); // true
console.log(arr instanceof Array); // true
2
3
上方利用 instanceof 判断一个变量是否属于某个对象的实例,因为在 JavaScript 中数组也是对象的一种,所以两个 console 都为 true。
# this 指向
var obj = {
name: "xiaoming",
getName: function () {
return this.name;
},
};
console.log(obj.getName()); // xiaoming
2
3
4
5
6
7
上方对象方法中的 this 指向对象本身,所以输出 xiaoming
var obj = {
name: "xiaoming",
getName: function () {
return this.name;
},
};
var nameFn = obj.getName;
console.log(nameFn()); // undefined
2
3
4
5
6
7
8
上方将对象中的方法赋值给了一个变量,此时方法中的 this 也将不再指向 obj 对象,从而指向 window 对象,所以 console 为 undefined。
var obj = {
name: "xiaoming",
getName: function () {
return this.name;
},
};
var obj2 = {
name: "xiaohua",
};
var nameFn = obj.getName;
console.log(nameFn.apply(obj2)); // xiaohua
2
3
4
5
6
7
8
9
10
11
12
13
上方同样将 obj 对象中的方法赋值给了变量 nameFn,但是通过 apply 方法将 this 指向了 obj2 对象,所以最终 console 为 xiaohua。
# 函数参数
function test6() {
console.log(Array.prototype.slice.call(arguments)); // [1,2]
}
test6(1, 2);
2
3
4
上方利用函数中的 arguments 类数组对象获取传入函数的参数数组,所以输出数组[1, 2]。
function test7() {
return function () {
console.log(Array.prototype.slice.call(arguments)); // 未执行到此,无输出
};
}
test7(1, 2);
2
3
4
5
6
上方同样利用 arguments 获取参数,但因 test7(1, 2)未执行 return 中的函数,所以无输出。若执行 test7(1, 2)(3, 4)则会输出[3, 4]。
var args = [1, 2];
function test8() {
console.log(Array.prototype.slice.call(arguments)); // [1,2,3,4]
}
Array.prototype.push.call(args,3, 4));
test8(...args);
2
3
4
5
6
上方利用 Array.prototype.push.call()方法向 args 数组中插入了 3 和 4,并利用 ES6 延展操作符(…)将数组展开并传入 test9,所以 console 为[1, 2, 3, 4]。
# 闭包问题
var elem = document.getElementsByTagName("div"); // 如果页面上有5个div
for (var i = 0; i < elem.length; i++) {
elem[i].onclick = function () {
alert(i); //总是5
};
}
2
3
4
5
6
上方是一个很常见闭包问题,点击任何 div 弹出的值总是 5,因为当你触发点击事件的时候 i 的值早已是 5,可以用下面方式解决:
var elem = document.getElementsByTagName("div"); // 如果页面上有5个div
for (var i = 0; i < elem.length; i++) {
(function (j) {
elem[j].onclick = function () {
alert(j); // 依次为 0,1,2,3,4
};
})(i);
}
2
3
4
5
6
7
8
在绑定点击事件外部封装一个立即执行函数,并将 i 传入该函数即可。
# 对象拷贝与赋值
var obj = {
name: "xiaoming",
age: 23,
};
var newObj = obj;
newObj.name = "xiaohua";
console.log(obj.name); // xiaohua
console.log(newObj.name); // xiaohua
2
3
4
5
6
7
8
上方我们将 obj 对象赋值给了 newObj 对象,从而改变 newObj 的 name 属性,但是 obj 对象的 name 属性也被篡改,这是因为实际上 newObj 对象获得的只是一个内存地址,而不是真正的拷贝,所以 obj 对象被篡改。
var obj2 = {
name: "xiaoming",
age: 23,
};
var newObj2 = Object.assign({}, obj2, { color: "blue" });
newObj2.name = "xiaohua";
console.log(obj2.name); // xiaoming
console.log(newObj2.name); // xiaohua
console.log(newObj2.color); // blue
2
3
4
5
6
7
8
9
上方利用 Object.assign()方法进行对象的深拷贝可以避免源对象被篡改的可能。因为 Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
var obj3 = {
name: "xiaoming",
age: 23,
};
var newObj3 = Object.create(obj3);
newObj3.name = "xiaohua";
console.log(obj3.name); // xiaoming
console.log(newObj3.name); // xiaohua
2
3
4
5
6
7
8
我们也可以使用 Object.create()方法进行对象的拷贝,Object.create()方法可以创建一个具有指定原型对象和属性的新对象。