未加星标

谈谈 JavaScript 中的 TDZ

字体大小 | |
[前端(javascript) 所属分类 前端(javascript) | 发布者 店小二04 | 时间 2017 | 作者 红领巾 ] 0人收藏点击收藏

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


谈谈 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 这个变量就没了,而等到 setTimeout 开始执行的时候 a[i] 里面的 i 也就是 undefined,输出的结果自然也是 undefined。

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

var a = [1,2,3]; for(var i=0; i<a.length;i++){ (function(b){ setTimeout(function(){ console.log(a); }, 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 两个函数哪个函数能运行成功呢?

碎碎念

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

微信公众号:程序员的诗和远方

公众号ID : MonkeyCoder-Life


谈谈 JavaScript 中的 TDZ
参考

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

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

分页:12
转载请注明
本文标题:谈谈 JavaScript 中的 TDZ
本站链接:http://www.codesec.net/view/534952.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(27)