[英]What is the purpose of let hoisting in ES6?
我知道let
将被提升到块的顶部,但是在初始化之前对其进行访问将由于处于Temporal Dead Zone
而引发ReferenceError
例如:
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吗?
该文件说:
实例化包含变量的Lexical Environment时将创建变量, 但是在评估变量的LexicalBinding之前,不能以任何方式对其进行访问 。 由带有初始化程序的LexicalBinding定义的变量在评估LexicalBinding时(而不是在创建变量时)被分配其初始化程序的AssignmentExpression的值。 如果let声明中的LexicalBinding没有初始化程序,则在评估LexicalBinding时会为变量分配未定义的值。
也是var关键字 :
let允许您声明范围仅限于使用它的块,语句或表达式的变量。 这与var关键字不同,该关键字在全局范围内或在整个函数本地定义变量,而不管块范围如何。
您也可以查看Kyle Simpson的这篇文章:支持和反对let
http://www.2ality.com/2015/10/why-tdz.html很好地解释了它,并且还链接到https://mail.mozilla.org/pipermail/es-discuss/2012-September/024996 .html ,是与此主题相关的讨论。
解释该问题的内容
为什么会有暂时的死区供
let
?
如果TDZ不会引起引用错误,并且您在声明变量之前(即在TDZ中)访问了变量,则您(很可能)会丢失编程错误。 导致参考错误的TDZ可帮助您捕获编程错误。
所以,你的下一个问题是- 为什么连一个TDZ let
? 为什么在声明变量时不启动let
变量的作用域? 答案是const
。 TDZ用于const
,(差) let
陷入TDZ只是为了更轻松地在let
和const
之间切换
var是否也受TDZ的困扰,我知道何时会抛出undefined,但这是因为TDZ吗?
不, var
不受TDZ的影响。 它不会引发任何错误。 除非另行设置,否则它始终是undefined
。 TDZ是ES6。
您必须首先了解起重。 将代码声明的初始化带到块的顶部,请考虑以下示例
function getValue(condition) {
if (condition) {
var value = "blue";
// other code
return value;
} else {
// value exists here with a value of undefined
return null;
}
// value exists here with a value of undefined
}
如您所见,可以在else和函数中访问该值。 由于在getValue(condition)函数之后直接声明了它。
function getValue(condition) {
if (condition) {
let value = "blue";
// other code
return value;
} else {
// value doesn't exist here
return null;
}
// value doesn't exist here
}
但是当我们使用时,您可以看到其中的区别。 这些示例摘自我正在阅读的书,建议您也去看看
https://leanpub.com/understandinges6/read#leanpub-auto-var-declarations-and-hoisting
进一步澄清
当访问时引发错误时,
let
升起至顶部的目的是什么?
这样,我们就可以拥有一个块范围,这是相当容易理解的概念, 而没有 var
hoisting的块等效功能,而var
hoisting是传统的错误和误解来源。
考虑一下此块的内部:
{
let a = 1;
console.log(a);
let b = 2;
console.log(a, b);
let c = 3;
console.log(a, b, c);
}
设计师在这里有三个主要选择:
var
在函数中一样); 要么 let
, const
, class
等开头的新作用域; 要么 选项1使我们容易遇到与var
提升相同的错误。 选项2 的方式变得更复杂为人们所理解,并为JavaScript引擎更多的工作要做(如果你想让他们详情见下文)。 选项3达到了最佳效果:块范围易于理解和实现,并且TDZ可以防止由var
提升引起的错误。
var
也受TDZ困扰吗,我知道什么时候会抛出undefined
但这是因为TDZ吗?
不, var
声明没有TDZ。 undefined
未抛出 ,它只是变量在声明时具有的值,但尚未设置为其他任何值。 var
声明被提升到函数或全局环境的顶部,并且即使在到达var
行之前,也可以在该范围中完全访问。
可能有助于了解如何在JavaScript中处理标识符解析:
该规范根据称为词法环境的术语对其进行定义,该词法环境包含一个环境记录 ,该记录包含有关当前上下文的变量,常量,函数参数(如果相关), class
声明等的信息。 ( 上下文是一个范围的具体执行也就是说,如果我们有一个调用的函数example
,体example
定义了一个新的范围;每次我们打电话时example
,有新的变量等,对于范围-这是上下文 。)
有关标识符(变量等)的信息称为绑定 。 它包含标识符的名称,它的当前值以及有关它的一些其他信息(例如,它是可变的还是不可变的,是否可以访问,等等)。
当代码执行进入一个新的上下文时(例如,当一个函数被调用时,或者我们输入一个包含let
或类似内容的块)时,JavaScript引擎会创建*一个新的词法环境对象(LEO),并带有其环境记录(envrec) ,并为LEO提供指向包含该LEO的“外部” LEO的链接,从而形成一条链。 当引擎需要查找标识符时,它会在最顶层LEO的envrec中查找绑定,如果找到,则使用它。 如果找不到,请查看链中的下一个LEO,依此类推,直到到达链末。 (您可能已经猜到:链中的最后一个链接是针对全球环境的。)
ES2015中用于启用块范围以及let
, const
等的更改基本上是:
考虑到所有这些,让我们看下面的代码:
function example() {
console.log("alpha");
var a = 1;
let b = 2;
if (Math.random() < 0.5) {
console.log("beta");
let c = 3;
var d = 4;
console.log("gamma");
let e = 5;
console.log(a, b, c, d, e);
}
}
调用example
,引擎将如何处理(至少就规范而言)? 像这样:
example
调用的上下文创建一个LEO a
, b
和d
绑定添加到该LEO的envrec中,所有绑定都具有undefined
值:
a
是因为它是位于函数中任何位置的var
绑定。 其“可访问”标志设置为true(由于var
)。 b
是因为它是函数顶层的let
绑定; 其“可访问”标志设置为false,因为我们尚未到达let b
行。 d
,因为它是一个var
结合,就像a
。 console.log("alpha")
。 a = 1
,将a
的绑定值从undefined
更改为1
。 let b
,将b
绑定的“ accessible”标志更改为true。 b = 2
,将b
的绑定值从undefined
更改为2
。 Math.random() < 0.5
; 可以说是真的: c
和e
绑定添加到该LEO的envrec,其“可访问”标志设置为false,值设置为undefined
。 console.log("beta")
。 let c
,将c
的绑定的“ accessible”标志设置为true c = 3
。 d = 4
。 console.log("gamma")
。 let e
,将e
的绑定的“ accessible”标志设置为true。 e = 5
。 console.log(a, b, c, d, e)
。 希望答案:
let
半吊装(使它易于理解范围,以避免过多的低地球轨道卫星和envrecs,并在像那些块级避免虫子var
提升在函数级别有) var
没有TDZ( var
变量的绑定的“ accessible”标志始终为true) *至少,这就是他们在规范方面所做的。 实际上,只要规范定义了,他们就可以做任何喜欢的事情。 实际上,大多数引擎会利用堆栈等来做效率更高的事情。
一个 从技术上讲,“ let
变量没有被悬挂。let
变量“被悬挂”是正确的,但在我看来,该术语的使用具有误导性。 一种描述语义的等效方法是,当您尝试在其声明上方引用它时,会得到一个ReferenceError
,因为它尚不存在。 如果尝试引用该块中任何地方都不存在的变量,也会得到相同的结果。
更多信息:
C ++和JavaScript都具有块作用域,但是在这一特定点上有所不同,因此我们可以通过了解它们的行为方式来理解这一点。 考虑以下示例:
#include <iostream>
int main() {
int x = 3;
{
std::cout << x << std::endl;
int x = 4;
}
return 0;
}
在C ++中,实际上没有任何提升,当cout
行运行时第二个x
不存在(它将x
打印到屏幕上),但是第一个x
仍然存在,因此程序乖乖地打印了3。这非常令人困惑。 相反,我们应该认为对x
引用是模棱两可的,并使其成为错误。
这是类似的JavaScript代码中发生的情况:
'use strict';
let x = 3;
(() => {
console.log(x);
let x = 4;
})();
在JavaScript中,此问题已通过“提升”第二个x
,但在访问时使其抛出ReferenceError
。 据我所知,这种“吊装”等效于应该使对x
的引用由于歧义性而成为错误。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.