美文网首页极乐科技Web前端之路前端开发那些事
由阮一峰老师的一条微博引发的 TDZ 思考

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

作者: 背着灯笼 | 来源:发表于2017-01-21 20:59 被阅读4532次

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


TDZ.jpeg

阮老师贴了一段代码,报错了,然后左思右想觉得这是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)

现在阮老师贴的那段代码为什么报错的原因就一目了然了吧!
注:如有不对的地方,欢迎指正!

引用

相关文章

网友评论

  • e1fbb095fb53:我突然发现 阮一峰自己的教程里有这个的解释啊
    ```
    var x = 1;

    function foo(x = x) {
    // ...
    }

    foo() // ReferenceError: x is not defined
    ```
    "上面代码中,参数x = x形成一个单独作用域。实际执行的是let x = x,由于暂时性死区的原因,这行代码会报错”x 未定义“。" http://es6.ruanyifeng.com/#docs/function#
    e1fbb095fb53:@背着灯笼 很可能是翻译的吧,当时稀里糊涂的。
    e1fbb095fb53:@背着灯笼 他写东西太多了,自己写的,自己也记不得
    背着灯笼:他发现自己错了, 还不改吗?:joy:
  • 大前端之路:区别就在于 var 和 let 声明的变量在发生声明提升时,初始化(initialisation)的行为不同导致的,用 var 声明的变量会初始化为undefined,而用 let 声明的变量会保持为未初始化(uninitialised)的状态。
    楼主,请问这个解释是哪里看到的。
    背着灯笼:下面的引用里就有
  • 林脉沐:outer inner这个例子,变量名没统一?
    背着灯笼:谢谢提醒~
  • 光哥很霸气:阮一峰是知道TDZ的好么,他只是以为参数的变量是var声明的。
    http://es6.ruanyifeng.com/#docs/let
  • 2f18e183f269:应该是默认参数会导致程序在严格模式下执行,所以引发暂时性死区问题。
    比如我们这么声明变量:
    var x = x // x 的值是 undefined,没有暂时性死区问题
    let x = x // 报错,x is not defined,有暂时性死区问题
  • 夏尔先生:恩,js函数是有自己作用域的,所以函数参数的声明方式就相当于let,根据博主的这个观点也就能够解释了~
    夏尔先生:@一袭白衣染 因为var是变量提升,所以var x=x不会报错,而let是声明提升,所以let x=x是会报错的。阮一峰的这一段代码报错了,也就证明了函数的参数是let~
    一袭白衣染:@夏尔先生 所以函数的参数是let…为啥啊
  • 黑色杜克: awesome
    背着灯笼:@黑色杜克 谢谢~
  • 极乐君:你以为阮一峰不知道占时性死区么,他之所以迷糊是他之前没认识到参数竟然有自己的作用域。
    e1fbb095fb53:这个说的其实不准确,在他自己书《Emacscript6 入门》中明确提到了“一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。” http://es6.ruanyifeng.com/#docs/function#函数参数的默认值
    极乐君:@yuyuyuo 似曾相识啊!!!
    8ec0f5423b42:支持!!!
  • 白羊座的白:Hoisting 不是 Hosting :smiley:
    背着灯笼:谢谢指正,已经改了~ 哈哈,我自己都忍不住笑了:joy:
  • magicalnight::joy: 老哥稳
  • zhilidali:楼主正解

本文标题:由阮一峰老师的一条微博引发的 TDZ 思考

本文链接:https://www.haomeiwen.com/subject/blhnbttx.html