原文地址:Don’t Use JavaScript Variables Without Knowing Temporal Dead Zone
让我问你一个小问题,以下2个代码片段,你觉得哪一个会报错?
片段1:在定义类之前实例化一个类:
new Car('red');
class Car {
constructor (color) {
this.color = color;
}
}
片段2:在定义方法之前调用该方法:
greet('World');
function greet(who) {
console.log("Hello " + who);
}
正确答案是:片段1,报得错误是:ReferenceError: Car is not defined ;片段2能正确执行
如果你的答案不是片段1,或者你只是猜测而不是真的知道原因,那么你就得好好理解 Temporal Dead Zone( TDZ )
在 JavaScript 中,对于关键字 let ,const 和 class 来说,TDZ是个非常重要的东西
1. 什么是 Temporal Dead Zone?
让我们从一个最基础的变量定义开始,如果你先定义然后初始化这个变量,然后再访问这个变量,那么一切都会正常执行
const white = '#FFFFFF';
console.log(white); // 输出:#FFFFFF
如果我们先访问这个变量,再定义的话
console.log(white); //抛异常:ReferenceError
const white = '#FFFFFF';
在表达式 const white=’#FFFFFF’ 之前的行, 就是变量 white的Temporal Dead Zone(禁制访问区域),在TDA访问 ‘white’会抛异常:ReferenceError: Cannot access ‘white’ before initialization

“Temporal Dead Zone”寓意定义变量之前,禁止访问这个变量。它给我们传递的信号是:未定义,勿访问
2. TDZ对表达式的影响
2.1 const 变量
如下例所示,const变量也受TDZ影响,在定义之前访问的话,会报错
pi; // throws `ReferenceError`
const pi = 3.14;
如果你在定义之后访问,则能正常执行
const pi = 3.14;
pi;
2.2 let 变量
let 变量受TDZ影响和cost变量一样
count; // throws `ReferenceError`
let count =10;
而在定义之后再访问,则正常
let count;
count; // => undefined
count = 10;
count; // => 10
2.3 class 类
如文章最开始的时候,在定义类之前,不能使用它
const myNissan = new Car('red'); // throws `ReferenceError`
class Car {
constructor (color) {
this.color = color;
}
}
如果你想正确使用,那么记得在定义之后再访问
class Car {
constructor (color) {
this.color = color;
}
}
const myNissan = new Car('red');
myNissan.color; // => 'red'
2.4 constructor()内的super()
如果你是继承了一个父类,那么在构造函数中,调用super()方法之前 ,this 就处于TDZ,请看如下代码:
class MuscleCar extends Car {
constructor (color, power) {
this.power = power;
super(color);
}
}
const myCar = new MuscleCar('blue', '300HP'); // `ReferenceError`
在构造函数内,在调用super()之前,this是不能使用的,TDZ 机制建议调用父类的构造函数来初始化实例,然后在子类的构造函数内对实例化的对象进行你想要的操作
2.5 函数参数
函数的默认参数处于一个全局作用域和函数作用域之间的 “中间作用域”( intermidiate scope ),这个中间区域的参数,依然要遵循TDZ的规则约束:
const a = 2;
function square(a = a) {
return a * a;
}
square(); // throws `ReferenceError`
” function square(a = a) ” ,是想表达a = a 与 const a= 2 是处于不同的作用域中,所以可以变量名一致。而 a = a 这个表达式相当于如下代码:
a;
let a = ?;
其中 a = a,可以分成两部分,第一部分是先执行右边,访问 a,拿到a的值,然后再执行左边 let a=?这个赋值操作。所以会出现定义之前访问 a 的问题。所以为了解决这个问题,可以把代码改成如下:
const init = 2;
function square(a = init) {
return a * a;
}
square(); // => 4
3. TDZ对var,function,import 的影响
一句话,TDZ对这三个关键字定义的对象不起任何作用,因为他们会自动提升到当前作用域最前面。你在var变量定义之前访问它,不会报错,会得到undefined
value; // => undefined
var value;
同时,你也可以在方法被定义之前,直接调用该方法
greet('World'); // => 'Hello, World!'
function greet(who) {
return `Hello, ${who}!`;
}
greet('Earth'); // => 'Hello, Earth!'
通常,我们对函数的实现是不太感兴趣的,这也是为什么有时候在函数定义之前调用它是有意义的。所以,import也能自动提升就是很有必要的
myFunction();
import { myFunction } from './myModule';
以上代码,能正常执行。但实际上,我们不建议这么做,import放在文件最开头是个好的编程习惯
4. 在TDZ获取变量类型
typeof 操作通用被我们用来判断当前作用域变量的类型,比如,一个名叫notDefined的变量,我们直接用typeof 去获取它类型的时候,不会报错:
console.log(typeof notDefined); //=> undefined
因为这个变量未定义,所以 typeof notDefined相当于undefined. 但是当你在TDZ区域使用 typeof 的时候,就会报引用错误,代码如下:
typeof variable; // throws `ReferenceError`
let variable;
所以,总体来说,在定义变量之前,任何访问该变量的操作都是不允许,会抛引用异常的
5. 不同作用域内的TDZ
TDZ只影响当前变量所在的作用域:

让我们来看一个详细例子:
function doSomething(someVal) {
// 函数作用域
typeof variable; // => undefined
if (someVal) {
// if内部块作用域
typeof variable; // throws `ReferenceError`
let variable;
}
}
doSomething(true);
通过这个示例我们能很明确的知道,一个变量的TDZ只对它当前所在的作用域有效果
6. 总结
TDZ是影响 const、let 和 class 语句可用性的重要概念,它不允许在声明之前使用变量。相反,如果想在声明之前就可以使用变量,那么你可以继续使用var来兼容。不过你应该避免那样做。因为在我看来,TDZ是语言规范中良好的编码实践之一