佳佳的博客
Menu
首页
IT
生活
学生
游戏
随机
【读书笔记】【深入理解ES6】#1-块级作用域绑定
IT
ECMAScript
JavaScript
《深入理解ES6》
2017-10-08
目录
## var声明及变量提升(Hoisting)机制 **在函数作用域或全局作用域中通过var关键字声明的变量,无论实际上是在哪里声明的,都会被当成在当前作用域顶部声明的变量。**这就是我们常说的**提升(Hoisting)机制**。 通过下面的getValue函数来说明: ```js function getValue(condition) { if (condition) { var value = "blue"; console.log(value); // blue } else { console.log(value); // undefined } console.log(value); // 再次打印value的值 return value; } ``` ```js getValue(true); // 打印结果 // blue // blue ``` ```js getValue(false); // 打印结果 // undefined // undefined ``` 之所以会这样就是因为上面说的提升机制。 在预编译阶段,JavaScript引擎会将上面的getValue函数修改成如下这样: ```js function getValue(condition) { var value; if (condition) { value = "blue"; console.log(value); // blue } else { console.log(value); // undefined } console.log(value); // 再次打印value的值 return value; } ``` 变量value的作用域被提升到了函数顶部,而不仅仅是在if块中。 这同其它大部分编程语言都不一样,很容易引起bug。 为此,ES6中引入了**块级作用域**来强化对变量生命周期的控制。 ## 块级声明 **块级声明用于声明在指定块的作用域之外无法访问的变量。** 块级作用域(亦称为词法作用域)存在于: - 函数内部 - 块中(字符{和}之间的区域) ### let声明 **let声明的用法与var相同。用let代替var来声明变量,就可以把变量的作用域限制在当前代码块中。** **由于let声明不会被提升,因此开发者通常将let声明语句放在封闭代码块的顶部,以便整个代码块都可以访问。** ```js function getValue(condition) { if (condition) { let value = "blue"; console.log(value); // blue } else { console.log(value); // Uncaught ReferenceError: value is not defined } console.log(value); // Uncaught ReferenceError: value is not defined return value; } ``` 调用该方法会在第6行和第9行出现如下异常: ```js Uncaught ReferenceError: value is not defined ``` ### 禁止重声明 **假设作用域中已经存在某个标识符,此时再使用let关键字声明它就会抛出错误。** ```js var a = 1 var a = 2 let a = 3 ``` 执行到let时会出现如下错误: ```js Uncaught SyntaxError: Identifier 'a' has already been declared ``` ### const声明 **使用const声明的是常量,其值一旦被设定后不可更改。** 因此,每个通过const声明的变量必须进行初始化。 ```js const maxItems = 30; const name; ``` 第二行代码会抛出如下错误: ```js Uncaught SyntaxError: Missing initializer in const declaration ``` ### const和let **const和let声明的都是块级标识符,所以常量也只在当前代码块内有效,一旦执行到块外会立即被销毁。** **常量同样也不会被提升至作用域顶部。** **与let类似,在同一作用域用const声明已经存在的标识符也会导致语法错误,无论该标识符是以var还是let声明的。** 这里我写代码测试时发现了一件比较奇怪的事情。 ```js var name = 'jiajia'; const name = 'ljj'; ``` 按照上面的定义,第二行代码应该抛出错误才对,结果却是正常的执行了。 当把变量名 name 改成 message 时,第二行代码是会正常的抛出错误的。 那就只能是 name 这个变量名的问题了,这个名字比较特殊。 特殊在哪里呢? 因为代码是在全局作用域执行的,使用 var 声明的变量会自动变成全局对象(浏览器环境中的 window 对象)的一个属性。 而 name 特殊在它是 window 的一个固有属性。 至于为什么固有属性会有这个特性我就不知道了。 ### 用const声明对象 **JS中常量如果是对象,则常量中的值是可以修改的。** *这同C#是一样的,java中好像也是这样的。* ```js const person = { name : 'JiaJia' } // 可以修改对象属性的值 person.name = 'ljj' // 抛出语法错误 person = { name : 'ljj' } ``` 最后的赋值会抛出如下错误: ```js Uncaught TypeError: Assignment to constant variable. ``` ### 临时死区(Temporal Dead Zone) 与var不同,let和const声明的变量不会被提升到作用域顶部。如果在声明之前访问这些变量,即使是相对安全的typeof操作符也会触发引用错误。 ```js function getValue(condition) { if (condition) { console.log(typeof value); // 引用错误 let value = "blue"; } } getValue(true); ``` `console.log(typeof value)` 会抛出如下错误: ```js Uncaught ReferenceError: value is not defined ``` 但在let声明的作用域外对该变量使用typeof则不会抛出错误: ```js function getValue(condition) { console.log(typeof value); // undefined if (condition) { let value = "blue"; } } getValue(true); ``` ## 循环中的块作用域绑定 最常见的for循环: ```js for (var i = 0; i < 10; i++) { } console.log(i); // 10 ``` 最终会打印10,说明i的作用域在循环体外,不是想象中的for循环内部。 将var改为let即可以实现想定的效果。 ```js for (let i = 0; i < 10; i++) { } // i在这里不可访问,抛出一个错误 console.log(i); // Uncaught ReferenceError: i is not defined ``` ### 循环中的函数 长久以来,var声明让开发者在循环中创建函数变得异常困难,因为变量到了循环之外仍能访问。 ```js var funcs = []; for (var i = 0; i < 10; i++) { funcs.push(function() { console.log(i); }); } funcs.forEach(function(func) { func(); // 输出10次数字10 }); ``` 预想的是输出0~9,但实际输出的是10次10。 是因为循环里的每次迭代都共享着变量i,循环内部创建的函数全部都保留了对相同变量的引用。循环结束时变量i值为10,所以调用 console.log 时就会输出数字10. 为了解决这个问题,开发者们在循环中使用了**立即调用函数表达式(IIFE)**,以**强制生成计数器变量的副本**。 ```js var funcs = []; for (var i = 0; i < 10; i++) { funcs.push(function(value) { return function() { console.log(value); } }(i)); } funcs.forEach(function(func) { func(); // 输出0~9 }); ``` 在循环内部,IIFE表达式为接受的每一个变量i都创建了一个副本并存储为变量value。这个变量就是响应迭代创建的函数所使用的值,因此调用每个函数都会像从0到9循环一样得到期望的值。 ### 循环中的let声明 **let声明模仿上述实例中IIFE所做的一切来简化循环过程,每次迭代都会创建一个新变量,并以之前迭代中同名变量的值将其初始化。** 不需要使用IIFE,只需将示例中的var改成let就可以得到想要的结果。 ```js var funcs = []; for (let i = 0; i < 10; i++) { funcs.push(function() { console.log(i); }); } funcs.forEach(function(func) { func(); // 输出0~9 }); ``` 对于for-in循环也是一样的。 ```js var funcs = [], object = { a: true, b: true, c: true }; for (let key in object) { funcs.push(function() { console.log(key); }); } funcs.forEach(function(func) { func(); // 输出a,b和c }); ``` 如果变量key使用var声明,则会打印三个c。 可见for-in循环与for循环的表现是一致的。 ### 循环中的const声明 **在ES6标准中没有明确指明不允许在循环中使用const声明,然而针对不同类型的循环它会表现出不同的行为。** 对于普通的for循环来说,可以在初始化时使用const,但是更改这个变量的值就会抛出错误。 ```js var funcs = []; // 第二次迭代时会抛出错误 for (const i = 0; i < 10; i++) { funcs.push(function() { console.log(i); }); } funcs.forEach(function(func) { func(); }); ``` 第二次迭代时会抛出如下错误: ```js Uncaught TypeError: Assignment to constant variable. ``` 在for-in或for-of循环中使用const时的行为与使用let一直。 ## 全局块作用域绑定 let和const与var的另外一个区别是它们在全局作用域中的行为。 当var被用于全局作用域时,它会创建一个新的全局变量作为全局对象(浏览器环境中的window对象)的属性。 这意味着var可能会无意中覆盖一个已经存在的全局变量。 ```js console.log(window.RegExp); // ƒ RegExp() { [native code] } var RegExp = "Hello!"; console.log(window.RegExp); // "Hello!" var ncz = "Hi!"; console.log(window.ncz); // "Hi!" ``` 全局变量RegExp会覆盖之前window中的RegExp属性(window中既存的一个方法),变成了一个字符串。 同样window中也多了一个ncz的属性。 如果你在全局作用域中使用let或const,会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性。换句话说,**用let或const不能覆盖全局变量,而只能遮蔽它。** > ### Note > 如果希望在全局对像下定义变量,仍然可以使用var。这种情况常见于浏览器中跨frame或跨window访问代码。 ## 块级绑定最佳实践的进化 ES6尚在开发时,人们普遍认为应该默认使用let而不是var。 然而,当更多的开发者迁移到ES6后,另一种做法日益普及:**默认使用const,只有在确实需要改变变量的值时使用let。**
版权声明:原创文章,未经允许不得转载。
https://www.liujiajia.me/2017/10/08/【读书笔记】【深入理解es6】1-块级作用域绑定/
« 【读书笔记】【深入理解ES6】#2-字符串和正则表达式
【C#】int vs Int32 »
昵称
*
电子邮箱
*
回复内容
*
(回复审核后才会显示)
提交