前言
本文是我学习 JavaScript 过程中收集与整理的一些易错知识点, 将分别从变量作用域, 类型比较, this 指向, 函数参数, 闭包问题及对象拷贝与赋值这 6 个方面进行由浅入深的介绍和讲解, 其中也涉及了一些 ES6 的知识点.
JavaScript 知识点
1. 变量作用域
- var a = 1;
- function test() {
- var a = 2;
- console.log(a); // 2
- }
- test();
上方的函数作用域中声明并赋值了 a, 且在 console 之上, 所以遵循就近原则输出 a 等于 2.
- var a = 1;
- function test2() {
- console.log(a); // undefined
- var a = 2;
- }
- test2();
上方的函数作用域中虽然声明并赋值了 a, 但位于 console 之下, a 变量被提升, 输出时已声明但尚未被赋值, 所以输出 "undefined".
- var a = 1;
- function test3() {
- console.log(a); // 1
- a = 2;
- }
- test3();
上方的函数作用域中 a 被重新赋值, 未被重新声明, 且位于 console 之下, 所以输出全局作用域中的 a.
- let b = 1;
- function test4() {
- console.log(b); // b is not defined
- let b = 2;
- }
- test4();
上方函数作用域中使用了 ES6 的 let 重新声明了变量 b, 而 let 不同于 var 其不存在变量提升的功能, 所以输出报错 "b is not defined".
- function test5() {
- let a = 1;
- {
- let a = 2;
- }
- console.log(a); // 1
- }
- test5();
上方的函数作用域中用 let 声明了 a 为 1, 并在块级作用域中声明了 a 为 2, 因为 console 并不在函数内的块级作用域中, 所以输出 1.
2. 类型比较
- var arr = [],
- arr2 = [1];
- console.log(arr === arr2); // false
上方两个不同的数组比较, console 为 false.
- var arr = [],
- arr2 = [];
- console.log(arr === arr2); // false
上方两个相同的数组比较, 因为两个单独的数组永不相等, 所以 console 为 false.
- var arr = [],
- arr2 = {};
- console.log(typeof(arr) === typeof(arr2)); // true
上方利用 typeof 比较数组和对象, 因为 typeof 获取 NULL, 数组, 对象的类型都为 object, 所以 console 为 true.
- var arr = [];
- console.log(arr instanceof Object); // true
- console.log(arr instanceof Array); // true
上方利用 instanceof 判断一个变量是否属于某个对象的实例, 因为在 JavaScript 中数组也是对象的一种, 所以两个 console 都为 true.
3.this 指向
- var obj = {
- name: 'xiaoming',
- getName: function () {
- return this.name
- }
- };
- console.log(obj.getName()); // 'xiaoming'
上方对象方法中的 this 指向对象本身, 所以输出 "xiaoming".
- var obj = {
- myName: 'xiaoming',
- getName: function () {
- return this.myName
- }
- };
- var nameFn = obj.getName;
- console.log(nameFn()); // undefined
上方将对象中的方法赋值给了一个变量, 此时方法中的 this 也将不再指向 obj 对象, 从而指向 window 对象, 所以 console 为 "undefined".
- var obj = {
- myName: 'xiaoming',
- getName: function () {
- return this.myName
- }
- };
- var obj2 = {
- myName: 'xiaohua'
- };
- var nameFn = obj.getName;
- console.log(nameFn.apply(obj2)); // 'xiaohua'
上方同样将 obj 对象中的方法赋值给了变量 nameFn, 但是通过 apply 方法将 this 指向了 obj2 对象, 所以最终 console 为'xiaohua'.
4. 函数参数
- function test6() {
- console.log(Array.prototype.slice.call(arguments)); // [1, 2]
- }
- test6(1, 2);
上方利用函数中的 arguments 类 shu'zu 对象获取传入函数的参数数组, 所以输出数组[1, 2].
- function test7 () {
- return function () {
- console.log(Array.prototype.slice.call(arguments)); // 未执行到此, 无输出
- }
- }
- test7(1, 2);
上方同样利用 arguments 获取参数, 但因 test7(1, 2)未执行 return 中的函数, 所以无输出, 若执行 test7(1, 2)(3, 4)则会输出[3, 4].
- var args = [1, 2];
- function test9() {
- console.log(Array.prototype.slice.call(arguments)); // [1, 2, 3, 4]
- }
- Array.prototype.push.call(args, 3, 4);
- test9(...args);
上方利用 Array.prototype.push.call()方法向 args 数组中插入了 3 和 4, 并利用 ES6 延展操作符 (...) 将数组展开并传入 test9, 所以 console 为[1, 2, 3, 4].
5. 闭包问题
- var elem = document.getElementsByTagName('div'); // 如果页面上有 5 个 div
- for(var i = 0; i < elem.length; i++) {
- elem[i].onclick = function () {
- alert(i); // 总是 5
- };
- }
上方是一个很常见闭包问题, 点击任何 div 弹出的值总是 5, 因为当你触发点击事件的时候 i 的值早已是 5, 可以用下面方式解决:
- var elem = document.getElementsByTagName('div'); // 如果页面上有 5 个 div
- for(var i = 0; i < elem.length; i++) {
- (function (w) {
- elem[w].onclick = function () {
- alert(w); // 依次为 0,1,2,3,4
- };
- })(i);
- }
在绑定点击事件外部封装一个立即执行函数, 并将 i 传入该函数即可.
6. 对象拷贝与赋值
- var obj = {
- name: 'xiaoming',
- age: 23
- };
- var newObj = obj;
- newObj.name = 'xiaohua';
- console.log(obj.name); // 'xiaohua'
- console.log(newObj.name); // 'xiaohua'
上方我们将 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'
上方利用 Object.assign()方法进行对象的深拷贝可以避免源对象被篡改的可能. 因为 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'
我们也可以使用 Object.create()方法进行对象的拷贝, Object.create()方法可以创建一个具有指定原型对象和属性的新对象.
结语
学习 JavaScript 是一个漫长的过程, 不能一蹴而就. 希望本文介绍的几点内容能够帮助学习 JavaScript 的同学更加深入的了解和掌握 JavaScript 的语法, 少走弯路.
来源: https://juejin.im/post/584268a561ff4b006c27c7b9