什么是时间死区?

197

我听说访问未初始化的letconst值可能会导致ReferenceError,因为存在一个叫做暂时性死区的东西。

什么是暂时性死区?它与作用域和提升有什么关系?在哪些情况下会遇到它?


7
可能是ES6中使用let或const声明的变量不会被提升吗?的重复问题-虽然该问题没有聚焦于TDZ,但答案基本相同。 - Bergi
3个回答

273

letconstvar 相比有两个主要的不同点:

  1. 它们是块级作用域
  2. 在声明前访问一个 var 的结果为 undefined;在声明前访问一个 letconst 会抛出 ReferenceError 异常:

console.log(aVar); // undefined
console.log(aLet); // Causes ReferenceError: Cannot access 'aLet' before initialization

var aVar = 1;
let aLet = 2;

这些例子表明,let声明(以及与之相同的const声明)可能不会被提升,因为在赋值之前似乎不存在aLet
然而,事实并非如此——letconstvarclassfunction一样被提升了,但是它们进入作用域和被声明之间有一个时间段,在此期间它们不能被访问。这个时间段就是临时死区(TDZ)。
aLet声明而不是赋值时,TDZ就结束了。

// console.log(aLet) // Would throw ReferenceError

let aLet;

console.log(aLet); // undefined
aLet = 10;
console.log(aLet); // 10

这个例子展示了let是被提升的:

let x = "outer value";

(function() {
  // Start TDZ for x.
  console.log(x);
  let x = "inner value"; // Declaration ends TDZ for x.
}());

来源:深入浅出解析时间死区(Temporal Dead Zone)

在内部作用域中访问x仍会导致ReferenceError。如果let没有被提升,它将记录outer value

时间死区是一件好事,因为它有助于突出错误——在声明之前访问值很少是有意的。

时间死区也适用于默认函数参数。参数从左到右进行评估,并且每个参数都在分配之前处于时间死区中:

// b is in TDZ until its value is assigned.
function testDefaults(a = b, b) { }

testDefaults(undefined, 1); // Throws ReferenceError because the evaluation of a reads b before it has been evaluated.

babel.js转译器中,默认情况下未启用TDZ。打开“高兼容性”模式以在REPL中使用。提供es6.spec.blockScoping标志,以在CLI或作为库中使用。

建议进一步阅读:TDZ揭秘深入理解ES6 Let、Const和“时间死区”(TDZ)


3
也很有趣:为什么有时会出现“时间死区”(temporal dead zone)? - a better oliver
2
我认为这是一篇很棒的文章!然而,我印象中 'let' 不受提升的影响?我在 Mozilla 文档中找到了这个:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_Dead_Zone_and_errors_with_let我并不想成为一个挑剔的人,只是好奇并愿意接受澄清。 - dmarges
它显示相反的结果。如果let变量被提升,那么输出将是“undefined”。let变量是块级作用域,因此JS编译器只关心IIFE中的let x,因为那是一个不同的块。即使这样,它仍然会抛出ReferenceError,因为在编译器看来,x变量不在变量表中。将IIFE中的let替换为var可以证明这一点,因为输出将是undefined。 - dmarges
2
@jeows MDN页面仍然说它们不会被提升。如果您确信自己的话,请尝试编辑它。我认为我应该发布一个关于这个问题的问题。 - doubleOrt
1
@joews 我认为,你可以说它们被提升了,但由于 TDZ 的存在,在声明之前无法访问它们;或者你可以说它们没有被提升,但 TDZ 会导致对它们的任何引用都抛出错误。实际上,这两种说法都是正确的。除非你是在抽象意义上使用“提升”这个术语,比如“提升=引擎知道该变量的存在”。是这样吗?此外,规范上对此有什么说法? - doubleOrt
显示剩余6条评论

17

变量提升:
letconstvar 都会被提升。
(意思是它们会被移动到作用域的顶部进行声明。)

初始化:

  • var 也经历了初始化过程,其值将被初始化为 undefined
  • letconst 没有经历初始化过程,因此它们的值仍然无法访问,尽管已经被声明了。 这就是为什么会进入“暂时性死区”。

简而言之:

变量提升过程: varletconst
初始化过程: var


我想也需要提一下 letconst 是块级作用域,而 var 是函数作用域。 - frontendherodk

9

对于 let 和 const 变量,基本上,Temporal Dead Zone 是一个区域

"在您的变量声明之前",

即在这些变量的值无法访问的区域内,将会抛出一个错误。

例如:

let sum = a + 5;        //---------
//some other code       //         | ------>  this is TDZ for variable a
                        //         |
console.log(sum)        //---------
let a = 5;

上述代码会出现错误。

当我们使用变量'a'的var关键字时,同样的代码将不会产生错误,例如:

var sum = a;                            
console.log(sum)     //prints undefined
var a = 5;

在第二个例子中,控制台日志会输出“NaN”(即将undefined5相加的结果)。变量var a的声明被提升了,但将a设置为5的初始化代码并没有。 - traktor
是的,没错,a被提升了但没有初始化。因此a将是未定义的。 - niranjan_harpale
第一个引用的例子不正确,请更正或删除它。 - Aman Jain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接