原文:ECMAScript 6 入门--暂时性死区
只要块级作用域内存在 let 或 const 命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部(作用域)的影响。
var tmp = 123;
if (true) {
tmp = 'abc'; //Cannot access 'bar' before initialization
let tmp;
}
上面代码中,存在全局变量 tmp ,但是块级作用域内 let 又声明了一个局部变量 tmp ,导致后者绑定这个块级作用域,所以在 let 声明变量前,对 tmp 赋值会报错。
ES6 明确规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就是用这些变量,就会报错。
总之,在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ)。
if (true) {
// TDZ开始
tmp = 'abc'; // Cannot access 'tmp' before initialization
console.log(tmp); // Cannot access 'tmp' before initialization
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
上面代码中,在 let 命令声明变量 tmp 之前,都属于变量 tmp 的“死区”。
“暂时性死区”也意味着 typeof 不再是一个百分之百安全的操作。
typeof x; // Cannot access 'x' before initialization
let x;
上面代码中,变量 x 使用 let 命令声明,所以在声明之前,都属于 x 的死区,只要用到该变量就会报错。因此,typeof 运行时就会抛出一个 ReferenceError 。
作为比较,如果一个变量根本没有被声明,使用 typeof 反而不会报错。
typeof y; // "undefined"
上面代码中,y 是一个不存在的变量名,结果返回 undefined 。所以,在没有 let 之前,typeof 运算符时百分之百安全的,永远不会报错。现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。
有些“死区”比较隐蔽,不太容易发现。
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // Cannot access 'y' before initialization
上面代码忠,调用 bar 函数之所以报错(某些实现可能不报错),是因为参数 x 默认值等于另一个参数 y ,而此时 y 还没有声明,属于“死区”。如果 y 的默认值是 x,就不会报错,因为此时 x 已经声明了。
function var (x = 2, y = x) {
return [x, y];
}
bar(); // [2, 2]
另外,下面的代码也会报错,与 var 的行为不同。
// 不报错
var x = x;
// 报错
let x = x; // Cannot access 'x' before initialization
上面代码报错,也是因为暂时性死区。使用 let 声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行就属于这个情况,在变量 x 的声明语句还没有执行完成前,就去取 x 的值,导致报错。
ES6 规定暂时性死区和 let 、const 语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。