由阮一峰老师的一条微博引发的 TDZ 思考

11,189 阅读4分钟

昨天(1月20号),阮老师在微博上发布了这样一条微博:


TDZ.jpeg

阮老师贴了一段代码,报错了,然后左思右想觉得这是V8的错,这微博一发下面引起了激烈的讨论......

微博原文在这里,大家可以去看看。评论里有人留言指出了, 这不是 V8 的 bug, 这是 TDZ 。那么究竟什么是 TDZ ?这段代码又为什么会报错?让我们来一一揭晓答案!

在了解 TDZ 之前, 让我们先来了解一下声明提升(hosting),这将会帮助我们理解 TDZ。

深入理解 hosting

关于 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() {
      // 内部的 x 变量在这里被声明,TDZ 开始的地方

      console.log(x) // ReferenceError

      let a = "inner" // x 变量被初始化, TDZ 结束
    })()

所以在内部的变量初始化之前访问 x 变量是会报错的,就因为有了 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)

现在阮老师贴的那段代码为什么报错的原因就一目了然了吧!

引用