我知道let
将被提升到块的顶部,但是在初始化之前访问它将抛出ReferenceError
,因为它位于Temporal Dead Zone
中
例如:
console.log(x); // Will throw Reference Error
let x = 'some value';
但是像这样的代码段运行起来不会出错:
foo(); // alerts foo;
function foo(){ // foo will be hoisted
alert("foo");
}
"我的问题"
当let
在访问时会抛出错误时,它被提升到顶部的目的是什么?另外,var
也会受到TDZ的影响吗?我知道它什么时候会抛出undefined
,但这是因为TDZ吗?
5条答案
按热度按时间ssgvzors1#
文档中写道:
变量在其包含的词法环境被示例化时被创建**,但是在变量的LexicalBinding被求值之前不能以任何方式被访问**。由具有初始化器的LexicalBinding定义的变量在LexicalBinding被求值时被赋予其初始化器的AssignmentExpression的值,如果let声明中的LexicalBinding没有Initializer,则在计算LexicalBinding时,变量将被赋值为undefined值。
此外,var keyword:
let允许您声明作用域仅限于使用它的块、语句或表达式的变量。这与var关键字不同,后者将变量定义为全局变量,或定义为整个函数的局部变量,而不考虑块作用域。
你也可以查看这篇文章由凯尔·辛普森:For and against
let
kh212irz2#
http://www.2ality.com/2015/10/why-tdz.html以一种很好的方式解释了它,还链接到https://mail.mozilla.org/pipermail/es-discuss/2012-September/024996.html,这是关于该主题的相关讨论。
解释此问题的内容
为什么
let
有一个时间死区?1.如果TDZ没有导致引用错误,而你在变量声明之前访问了它(即在TDZ中),你(很可能)会错过一个编程错误。导致引用错误的TDZ可以帮助你发现编程错误。
1.因此,您的下一个问题是-为什么要为
let
设置TDZ?为什么不在声明let
变量时启动它的作用域?答案是const
。TDZ是针对const
的,而(较差的)let
使用TDZ只是为了使在let
和const
之间切换更容易同样,var也会受到TDZ的影响吗?我知道它什么时候会抛出未定义,但这是因为TDZ吗?
不,
var
不受TDZ的影响。它不抛出任何错误。它只是undefined
,直到设置为其他。TDZ是一个ES6的东西。eblbsuwk3#
你必须首先理解提升。它是把代码声明的初始化带到块的顶部,考虑下面的例子
你可以看到这个值可以在else和function中访问,因为它是在getValue(condition)函数之后直接声明的。
但是当我们使用的时候,让你可以看出区别。这些例子取自我正在阅读的一本书,推荐你也看看
https://leanpub.com/understandinges6/read#leanpub-auto-var-declarations-and-hoisting
以供进一步澄清
rekjcdws4#
当
let
在访问时抛出错误时,它被提升到顶部的目的是什么?这样我们就可以有块作用域,这是一个很容易理解的概念,* 而不是 * 有
var
提升的块等效物,这是错误和误解的传统来源。考虑这个块的内部:
设计师有三个主要选择:
1.拥有块作用域,但是所有声明都被提升到顶部并且可以访问(就像
var
在函数中);或1.不具有块作用域,而是具有从每个
let
、const
、class
等开始的新作用域;或1.具有块作用域,带有提升(或者我称之为“半提升”),其中声明被提升,但是它们声明的标识符在代码中到达之前是不可访问的
选项1会让我们遇到与
var
提升相同类型的bug;选项2对人们来说要复杂得多,JavaScript引擎要做的工作也更多(如果你想了解详细信息,请参见下文);选项3则是最佳选择:块作用域易于理解和实现,TDZ可以防止由var
提升引起的bug。另外,
var
也会受到TDZ的影响吗?我知道它什么时候会抛出undefined
,但这是因为TDZ吗?不,
var
声明没有TDZ。undefined
不是 * throw *,它只是变量声明时的值,但尚未设置为其他任何值。var
声明被提升到函数或全局环境的顶部,并且在该作用域中完全可访问,甚至在到达var
行之前。了解JavaScript中如何处理标识符解析可能会有所帮助:
规范用一种叫做 * 词法环境 * 的东西来定义它,它包含一个 * 环境记录 *,环境记录包含有关变量、常量、函数参数的信息(如果相关)、
class
声明等。(一个 context 是一个作用域的特定执行,也就是说,如果我们有一个名为example
的函数,example
的函数体定义了一个新的作用域;每当我们调用example
时,就会有新的变量等等,用于那个作用域--这就是 context。)关于标识符(变量等)的信息称为 binding,它包含标识符的名称、当前值和其他一些信息(比如它是可变的还是不可变的、是否可访问等)。
当代码执行进入新上下文时(例如,当调用一个函数时,或者我们输入一个包含
let
或类似字符的块时),JavaScript引擎会创建 * 一个新的词法环境对象(低地轨道)及其环境记录(envrec),并给LEO一个链接到包含它的“外部”LEO,形成一个链,当引擎需要查找标识符时,它在最顶层LEO的envrec中查找绑定,如果找到,则使用它;如果没有找到,则查看链中的下一个LEO,依此类推,直到到达链的末尾。(您可能已经猜到:链条的最后一环是全球环境。)ES 2015中启用数据块范围和
let
、const
等的更改基本上是:记住所有这些,让我们看看这段代码:
当
example
被调用时,引擎是如何处理的(至少在规范方面)?1.它为调用
example
的上下文创建一个LEO1.它将
a
、b
和d
的绑定添加到该LEO的envrec中,所有绑定的值都是undefined
:a
是因为它是位于函数中任何位置的var
绑定。它的“accessible”标志设置为true(因为var
)。b
是因为它是函数顶层的let
绑定;它的“accessible”标志被设置为false,因为我们还没有到达let b
行。var
绑定,就像a
一样。1.它执行
console.log("alpha")
。1.它执行
a = 1
,将a
的绑定值从undefined
更改为1
。1.它执行
let b
,将b
绑定的“accessible”标志更改为true。1.它执行
b = 2
,将b
的绑定值从undefined
更改为2
。1.它计算
Math.random() < 0.5
;假设这是真:1.因为块包含块范围的标识符,所以引擎为块创建新的LEO,将其“外部”LEO设置为步骤1中创建的LEO。
1.它将
c
和e
的绑定添加到LEO的envrec中,并将它们的“accessible”标志设置为false。1.它执行
console.log("beta")
。1.它执行
let c = 3
,将c
绑定的“accessible”标志设置为true,并将其值设置为3
1.它执行
d = 4
。1.它执行
console.log("gamma")
。1.它执行
let e = 5
,将e
绑定的“accessible”标志设置为true,并将其值设置为5
。1.它执行
console.log(a, b, c, d, e)
。希望答案是:
let
half-howiling(为了更容易理解作用域,避免有太多的LEO和envrec,并避免块级别的bug,如var
howiling在函数级别的bug)var
没有TDZ(var
变量绑定的“accessible”标志总是true)dpiehjr45#
一个
let
变量没有被提升。说一个let
变量被'提升'在技术上是正确的,但是在我看来这个术语的使用是误导的。描述语义的一个等价的方法是当你试图在它的声明之上引用它时,你得到一个ReferenceError
,因为它还不存在;如果你试图引用一个不存在于该块中的变量,你会得到同样的结果。更多信息:
C++和JavaScript都有块作用域,但在这一点上有所不同,所以我们可以通过理解它们的不同行为来理解这一点。
在C++中,实际上没有提升,当
cout
行运行时(将x
打印到屏幕),第二个x
不存在,但第一个x
仍然存在,所以程序顺从地打印3。这非常令人困惑。我们应该认为对x
的引用是不明确的,并将其设置为错误。下面是类似的JavaScript代码:
在JavaScript中,这个问题已经通过"提升"第二个
x
得到了解决,但是在访问时抛出了一个ReferenceError
。据我所知,这个"提升"相当于由于歧义而使对x
的引用出错,这是应该的。