谈谈 JavaScript 中的 TDZ

本来过年期间想写这个的,不过要准备些东西,一直没抽出时间,刚好今天有点空闲。上个月阮一峰阮老师在微博上发布了这样一条信息

于是评论区炸开了锅,很多人留言指出,这是 TDZ。 TDZ 全名 Temporal Dead Zone,翻译过来就是暂时性死区。今天就简单谈一下,我运行代码的环境是 node 6.9.1 。

JS 中的变量提升

我们都知道,在 JS 中,使用 var 声明的变量会被提升(Hosting),也就是不管你在什么地方写的 var,都会把其提升到作用域最开头。比如:

x = 5;
console.log(x);  // 5
var x;

这段代码可以运行,除了丑陋了点,写的顺序上,你先用了变量 x,然后才声明 x。其实 JS 引擎在执行这段代码的时候,会自动把声明给提升到最前面,也就是:

var x;
x = 5;
console.log(x);  // 5

这时我们再看另一个例子:

console.log(x);  // undefined
var x = 5;

这个例子输出的是 undefined,说明提升的时候仅仅是提升了声明,并没有把初始化也提升上去,而且在用 var 声明的时候如果没有赋值,JS 引擎就是自动初始化为『undefined』。

搞懂了这个,我们再来看一个作用域有关的例子:

var a = [1,2,3];
for(var i=0; i<a.length;i+=1){
    setTimeout(function(){
        console.log(a[i]);
    }, i*1000)
}

这里我们本想隔一秒输出一个数字的,但是实际上是输出了三次 undefined,这里是因为循环结束之后,i 的值变为3,而等到 setTimeout 开始执行的时候 a[i] 就变成 a[3],输出的结果自然就是 undefined。

如果想实现上面的功能,就要用到我们的闭包,把变量 i 给留住:

var a = [1,2,3];
for(var i=0; i<a.length;i++){
    (function(b){
        setTimeout(function(){
            console.log(a[b]);
        }, i*1000)
    })(i)
}

未初始化(uninitialised)

使用 var 进行变量声明时如果没有初始化,会自动初始化为 undefined,这个特性和变量提升结合在一起,常常会造成一些难以定位的 bug,因为眼睛容易被欺骗,看起来作用域范围是合理的。

这可能是 JavaScript 设计之初的一个错误,但是为了保持向后兼容,意味着永远改变不了 JavaScript 在浏览器中的行为。当 JavaScript 的发明人 Brendan Eich 决定修复这个问题时,他添加了一个新的关键词,就是 『let』。

let 和 var 一样,也可以用来声明变量,但是有很多好处:

  • let 声明的变量是块级作用域的。也就是 let 声明的变量只是外层块,而不是整个外层函数。
  • let 声明的全局变量不是全局对象的属性。 所以,你不可以通过 window.xxx 的方式访问 let 声明的变量。
  • 在 for 循环中,每次迭代都会创建新的绑定。这句话说的比较拗口,直接看上面那个例子,我们做一点修改,
var a = [1,2,3];
for(let i=0; i<a.length;i+=1){
    setTimeout(function(){
        console.log(a[i]);
    }, i*1000)
}

我们把 for(var i=0; i<a.length;i++) 改为 for(let i=0; i<a.length;i++) 只是改了声明变量 i 的方式,在 node 命令行中可以看到是可以正常输出 1,2,3 的。

  • 用 let 重新定义一个变量时会抛出语法错误。
let a = 1;
let a = 2;

这里抛出错误: SyntaxError: Identifier 'a' has already been declared。

  • let 声明的变量知道运行到被定义的代码时才会被初始化,所以在初始化之前使用会触发错误。这条其实是这里比较重要的一个点,简单来说,let 不会自动初始化成 undefined,如果你在初始化前用了 let 声明的变量,那么不好意思,引擎不想说话,并会丢一个 ReferenceError 给你。而且 let 声明的变量也会被提升,于是乱用就等着接异常吧。看个例子:
console.log(a);
let a = 1;

运行时直接就会报:ReferenceError: a is not defined。 上面的代码就类似于:

let a;
console.log(a);
a = 1;

如果只用 let 对变量进行声明,而没有做初始化,那么变量就会是未初始化(uninitialised)状态,在这个状态下你只要用这个变量,就会接到引擎抛来的异常。而变量从仅声明未初始化状态到初始化完成之间这个等待的时间,就叫做 TDZ 暂时性死区(这铺垫,好长)。

弄清楚这些之后,我们再来看看阮老师的代码为什么会被 V8 报错。

TDZ

ES6 中默认参数机制就是类似 let 声明一样,于是,阮老师的代码可以理解为:

var x = 1;
function foo( let x = x){
    console.log(x);
}
foo()

这里,let x = x。作为默认参数值的 x 和 作为形参的 x 撞车了,默认参数值 x 需要参数 x 的值,而参数 x 又需要默认参数 x 的值,于是无法完成初始化,形成暂时性死区。这里的,作用域只在函数内,和外面用 var 声明的 x 没关系。

要想运行,把形参值换一下就好:

var x = 1;
function foo(a = x){
    console.log(a);
}
foo()

这样就可以顺利输出了。

现在来考虑以下两种情况:

function a(x = y, y) {
    console.log(x);
}
function b(x, y = x){
    console.log(y);
}
a(undefined, 1)
b(1, undefined)

你认为 a,b 两个函数哪个函数能运行成功呢?

碎碎念

记录一些所思所想,写写科技与人文,写写生活状态,写写读书感悟,欢迎关注,交流。

参考

http://stackoverflow.com/questions/31219420/are-variables-declared-with-let-or-const-not-hoisted-in-es6

http://stackoverflow.com/questions/33198849/what-is-the-temporal-dead-zone

http://www.infoq.com/cn/articles/es6-in-depth-let-and-const

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏SeanCheney的专栏

Python解答LeetCode

古有科举八股,今有LeetCode。 八股定格式而取文采心意,LeetCode定题目且重答案背诵。 美其名曰:"practice makes perfec...

74516
来自专栏雪胖纸的玩蛇日常

第二章 基本数据结构

2028
来自专栏为数不多的Android技巧

ART深度探索开篇:从Method Hook谈起

Android上的热修复框架 AndFix 想必已经是耳熟能详,它的原理实际上很简单:方法替换——Java层的每一个方法在虚拟机实现里面都对应着一个ArtMet...

2001
来自专栏TungHsu

这或许是对小白最友好的python入门了吧——13,字典初识

前边我们学习了列表、元组等储存元素的方式,今天我们来看一个更为强大的:字典。 像列表用方括号[]、元组用圆括号()一样,字典用花括号{} 先编写一个简单的词典,...

3475
来自专栏企鹅号快讯

编程语言学啥?当然首选Python啦!千字长文教你如何入门Python!

1.1 流程控制之for循环 ? 1.2 开发工具IDE 1.2.1 为何要用IDE ? 很多语言都有比较流行的开发工具,比如JAVA 的Eclipse, C#...

4396
来自专栏前端达人

JavaScript基础——Promise使用指南

在上篇文章里《JavaScript基础——回调(callback)是什么》我们一起学习了回调,明白了回调就是一个在另外一个函数执行完后要执行的函数,如果我们希望...

2843
来自专栏互联网杂技

Javascript 中的神器——Promise

Promise in js 回调函数真正的问题在于他剥夺了我们使用 return 和 throw 这些关键字的能力。而 Promise 很好地解决了这一切。 2...

3485
来自专栏大前端_Web

字符集和字符编码(Charset & Encoding)

计算机中储存的信息都是用二进制数表示的;而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。通俗的说,按照何种规则将字符存储在计算机中,如’a’用什么...

2703
来自专栏向治洪

python 日期与时间

###python 日期与时间 (time,datetime包) [toc] #####概述 在应用程序的开发过程中,难免要跟日期、时间处理打交道。如:记录一个...

38410
来自专栏偏前端工程师的驿站

(cljs/run-at (JSVM. :all) "一次说白DataType、Record和Protocol")

前言  在项目中我们一般会为实际问题域定义领域数据模型,譬如开发VDOM时自然而言就会定义个VNode数据类型,用于打包存储、操作相关数据。clj/cljs不单...

1938

扫码关注云+社区

领取腾讯云代金券