昨天(1月20号),阮老师在微博上发布了这样一条微博:
阮老师贴了一段代码,报错了,然后左思右想觉得这是V8的错,这微博一发下面引起了激烈的讨论......
微博原文在这里,大家可以去看看。评论里有人留言指出了, 这不是 V8 的 bug, 这是 TDZ 。那么究竟什么是 TDZ ?这段代码又为什么会报错?让我们来一一揭晓答案!
在了解 TDZ 之前, 让我们先来了解一下声明提升(hoisting),这将会帮助我们理解 TDZ。
深入理解 Hoisting
关于 ES5 中的声明提升,即使用 var 声明的变量的声明提升, 我想大家应该都很熟悉了,这里我就不在赘述了。有人说在 ES6 中用 let 和 const 声明的变量不会发生声明提升,那究竟是不是这样呢?我们先来看一段代码。
console.log(a) // ReferenceError
let a = 1
运行这段代码会报错,如把上边代码中的 let 换成 var 就不会报错, 会输出 undefined,那这是不是就说明用 let 声明的变量不会发生声明提升呢?
我们先假设用 let 声明的变量不会发生声明提升,看看会发生什么?
var a = 1
;(function() {
console.log(a)
let a = 2
})()
按照我们预先的假设, 如果用 let 声明的变量不会发生声明提升的话, 这里 console 语句输出的结果应该是 1。但是这里也会报 ReferenceError 的错误。这说明了用 let 声明的变量不会发生声明提升这一结论是错误的!那么用 let 声明的变量究竟会不会发生声明提升的现象呢?
答案是会发生声明提升,用 let/const/class
声明的变量均会发生声明提升?既然用 let 声明的变量会出现声明提升的现象, 那之前的报错(ReferenceError)又该怎么解释呢?
区别就在于 var 和 let 声明的变量在发生声明提升时,初始化(initialisation)的行为不同导致的,用 var 声明的变量会初始化为undefined
,而用 let 声明的变量会保持为未初始化(uninitialised)的状态。也就是这样:
(function() {
console.log(a) // undefined, a 在这里会被初始化为 undefined
console.log(b) // ReferenceError, b 在这里会保持为未初始化的状态不变
var a = 1
let b = 2
})()
这也就解释了为什么会报错的那个问题, 到这里我就对变量声明提升有了更深刻的理解。那么什么是 TDZ 呢?
什么是 TDZ ?
ECMAScript 标准里并没有给出 TDZ(全称 Temporal Dead Zone)定义,这是 JS 社区里提出来的一种叫法。TDZ 并不是某个地方, 或是内存中的某个区域,而是变量被声明和被初始化之间的这段时间。我们来看一个例子:
let a = "outer"
;(function() {
// 内部的 a 变量在这里被声明,TDZ 开始的地方
console.log(a) // ReferenceError
let a = "inner" // a 变量被初始化, TDZ 结束
})()
所以在内部的变量初始化之前访问 a 变量是会报错的,就因为有了 TDZ 我们更容易发现 bug。为了更好的理解 TDZ 我们再看一个例子:
var a = a // 没有问题
let b = b // ReferenceError,在这里 b 并没有完全的被初始化, 还处在 TDZ 当中
好了铺垫了这么多, 说了这么久, 我们来看看阮老师的那段代码为什么会报错?
为什么会报错?
下面我们来看看 TDZ 函数默认参数中的应用。
我们都知道 ES6 中函数默认参数的计算顺序是从左到右进行的,那么像下面的这种写法是有问题的。
function foo(a = b, b) {
console.log(a)
}
foo(undefined, 1)
在上面的代码中,函数的参数 a 想要得到参数 b 的值,而参数 b 这时还未被初始化, 处在 TDZ 中, 所以会报 ReferenceError 的错误,而下面就不会出现错误。
function foo(a, b = a) {
// 这里的 a 已经被初始化,所以 b 可以取到 a 的值
console.log(b)
}
foo(1, undefined)
现在阮老师贴的那段代码为什么报错的原因就一目了然了吧!
注:如有不对的地方,欢迎指正!
引用
- http://dmitrysoshnikov.com/ecmascript/es6-notes-default-values-of-parameters/#tdz-temporal-dead-zone-for-parameters
- Understanding ECMAScript 6
- http://stackoverflow.com/questions/33198849/what-is-the-temporal-dead-zone
- http://stackoverflow.com/questions/31219420/are-variables-declared-with-let-or-const-not-hoisted-in-es6