谈起暂时性死区(TDZ)就不得不提 JavaScript 的变量提升特性。
在 JS 中,var、let、const 声明的变量都存在变量提升,区别在于:var 提升后会被初始化为 undefined 可以访问,而 let 和 const 提升后不会初始化,强行访问会报错。
乍一看有些奇怪——既然访问会报错,let 和 const 似乎没有提升的必要,毕竟"报错"和"访问一个不存在的变量"效果好像一样。但实际上这两种报错并不相同:
Uncaught ReferenceError: Cannot access '*' before initializationUncaught ReferenceError: * is not defined为什么要区分这两种错误?看下面这个例子:
js
js
如果把 let 换成 var,console.log 会打印 123,因为 var 没有块级作用域,if 块内外的 tmp 是同一个变量。但 let 具有块级作用域,if 块内是一个独立的作用域,块内的 let tmp 和外部的 var tmp 是两个不同的变量。
此时如果 let 不提升,console.log 就会"穿透"到外层作用域,读到外部的 123——但这会造成困惑,因为开发者在这个块里声明了自己的 tmp,却在声明前读到了一个"别人的" tmp。提升的意义正在于此:让引擎在进入块级作用域时就感知到 tmp 属于这里,从而阻止对外部同名变量的访问。
那为什么不直接报"变量未定义",而要专门区分一种新的报错类型?因为从整体上下文来看,tmp 确实是存在的(外部有 var tmp),报"未定义"会产生误导。TDZ 的报错更精确地告诉你:变量存在,但你在它初始化之前就访问了它。
最后看这种情况:
js
js
打印的是 123 而不是 undefined。原因是 var 没有块级作用域,if 块内外的 tmp 是同一个变量,变量提升只会发生一次,重复的无赋值声明会被忽略,已有的值自然得以保留。