[关闭]
@Dale-Lin 2018-12-02T22:46:41.000000Z 字数 2829 阅读 897

ES6 块级作用域绑定

深入理解ES6


ES6引入块级作用域来强化变量生命周期的控制

块级声明

ES6块级作用于存在于:

let声明

let声明用法和var相同,但可以把变量的作用域限制在当前代码块中(不会被提升)。

通常在封闭代码块的顶部使用let声明。

禁止重声明

若块级作用域中已存在某个标识符,再使用let声明它会抛出语法错误:

  1. var count = 30;
  2. //throw a syntax error
  3. let count = 40;

但在某作用域的内嵌作用域中,可以使用let声明同名变量:

  1. var count = 30;
  2. if (condition){
  3. let count = 40;
  4. }

const声明

const关键字声明的是常量,其值一旦设定后不可更改。

使用const声明的常量必须进行初始化。

  1. const maxItem = 30;
  2. //throw a syntax error
  3. const name;
  4. //throw a syntax error
  5. maxItem = 20;

在同一作用域中用const声明已存在的标识符也会导致语法错误。

  1. var message = 'Hello~';
  2. let age = 18;
  3. //both two will throw an error
  4. const message = 'goodbye!'
  5. const age = 19;

const 与 let

const 和 let 声明的都是块级标识符,只在当前代码块中有效,且不会被提升。

  1. if (condition){
  2. const maxItem = 5;
  3. }
  4. //can't access maxItem here

用const声明对象

const声明不允许修改绑定,但是可以修改值。意味着在声明对象后,可以修改该对象的属性值:

  1. const person = {
  2. name: 'Nicholas'
  3. };
  4. //can change prop's value
  5. person.name = 'Greg';

临时死区

由于let/const声明的变量不会像var声明的那样提升,如果在声明之前访问这些变量,即便是相对安全的typeof操作符也会触发引用错误

  1. if (condition){
  2. console.log(typeof value); //引用错误
  3. let value = 'blue';
  4. }

使用console.log的操作会触发错误,故使用let初始化变量value的语句不会执行。此时value就位于“临时死区”(temporal dead zone)或TDZ中。换成const也一样。

原因是JS引擎扫描发现变量的时候,要么将其提升到作用域顶部(var声明),要么将声明放在TDZ中(let/const声明)。访问TDZ中的变量会触发运行时错误,只有执行过变量声明语句后,变量才会从TDZ中移出,然后方可正常访问。

但在let声明的作用域外使用typeof操作符不会报错,因为并不在这个TDZ中:

  1. console.log(typeof value); //undefined
  2. if (condition) {
  3. let value = 'blue';
  4. }

循环中的块级作用域绑定

在for循环中使用let声明可以防止循环后变量i在其他地方被访问:

  1. for (let i = 0; i < 10; i++) {
  2. process(item[i]);
  3. }
  4. console.log(i); //不可访问,抛出错误

循环中的函数

var声明使得在循环中创建函数变得异常困难,因为变量到了循环之外仍能访问:

  1. var funcs = [];
  2. for (var i = 0; i < 10; i++) {
  3. funcs.push(function() {
  4. console.log(i);
  5. });
  6. }
  7. //输出10次10
  8. funcs.forEach(function(func) {
  9. func();
  10. }};

ES5使用立即调用函数来处理问题:

  1. var funcs = [];
  2. for (var i = 0; i < 10; i++){
  3. funcs.push((function(value){
  4. return function() {
  5. console.log(value);
  6. };
  7. }(i)));
  8. }
  9. funcs.forEach(function(func){func();});

原理是用一个变量value储存每次i的副本。

ES6的let和const提供的块级绑定让函数中的循环可以预期运行。

循环中的let声明

let声明模仿上述例子中IIFE来简化循环过程,每次迭代循环会创建一个新变量,并以之前迭代中同名变量的值将其初始化:

  1. var funcs = [];
  2. for (let i = 0; i < 10; i++){
  3. funcs.push(function(){
  4. console.log(i);
  5. })
  6. }
  7. funcs.forEach(function(func) {
  8. func(); //0,1,2,3,4,5,6,7,8,9
  9. })

每次循环,let声明都会创建一个新变量i,并将其初始化为i的当前值。

对于for-in循环、for-of循环也是一样的:

  1. var funcs = [],
  2. object = {
  3. a: true,
  4. b: true,
  5. c: true
  6. };
  7. for (let key in object) {
  8. funcs.push(function(){
  9. console.log(key);
  10. })
  11. }
  12. funcs.forEach(function(func){
  13. func(); //a, b, c
  14. });

循环中的const声明

注意const声明的变量不能被修改,否则会抛出错误,常规的for循环会发生这个问题。
在for-in或for-of循环中使用const时的行为和let一样:

  1. var funcs = [],
  2. object = {
  3. a: true,
  4. b: true,
  5. c: true
  6. };
  7. for (const key in object){
  8. funcs.push(function(){
  9. console.log(key);
  10. });
  11. }
  12. funcs.forEach(function(func){
  13. func(); //a, b, c
  14. });

循环内不改变key的值不会触发错误,因为在for-in或for-of循环中,每次迭代不会修改已有绑定,而是会创建一个

全局块作用域绑定

在全局作用域下,用var声明的变量会作为全局对象(浏览器中为window对象)的属性。
但在浏览器全局作用域下,使用let/const声明的变量不会成为window的属性,但window对象的属性会出现在全局作用域中:

  1. let a = 'bbb';
  2. const b = 'ccc';
  3. window.a; //undefined
  4. window.b; //undefined
  5. 'a' in window; //false
  1. window.a = 'bbb';
  2. //抛出语法错误!
  3. let a = 'ccc';

用let/const创建了一个绑定并遮蔽了全局的变量,但不会破坏全局作用域。

如果不想为全局对象创建属性,则使用let或const代替var会安全得多。

如果希望在全局对象下定义变量,仍然可以使用var。这种情况常用于在浏览器中跨frame或跨window访问代码。

总结

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注