繁体   English   中英

在ES6中起吊的目的是什么?

[英]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

  1. 如果TDZ不会引起引用错误,并且您在声明变量之前(即在TDZ中)访问了变量,则您(很可能)会丢失编程错误。 导致参考错误的TDZ可帮助您捕获编程错误。

  2. 所以,你的下一个问题是- 为什么连一个TDZ let 为什么在声明变量时不启动let变量的作用域? 答案是const TDZ用于const ,(差) let陷入TDZ只是为了更轻松地在letconst之间切换


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);
}

设计师在这里有三个主要选择:

  1. 具有块范围,但所有声明都放在顶部并且可以访问(就像var在函数中一样); 要么
  2. 没有块作用域,而是以每个letconstclass等开头的新作用域; 要么
  3. 具有块级作用域,具有提升(或所谓的“半提升”)功能,在该范围内可以悬挂声明,但是直到在代码中到达它们为止,它们声明的标识符才可访问

选项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中用于启用块范围以及letconst等的更改基本上是:

  • 如果该块包含块范围的声明,则可以为该块创建新的LEO
  • LEO中的绑定可能被标记为“不可访问”,因此可以强制执行TDZ

考虑到所有这些,让我们看下面的代码:

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 ,引擎将如何处理(至少就规范而言)? 像这样:

  1. 它为example调用的上下文创建一个LEO
  2. 它将abd绑定添加到该LEO的envrec中,所有绑定都具有undefined值:
    • 添加a是因为它是位于函数中任何位置的var绑定。 其“可访问”标志设置为true(由于var )。
    • 添加b是因为它是函数顶层的let绑定; 其“可访问”标志设置为false,因为我们尚未到达let b行。
    • d ,因为它是一个var结合,就像a
  3. 它执行console.log("alpha")
  4. 它执行a = 1 ,将a的绑定值从undefined更改为1
  5. 它执行let b ,将b绑定的“ accessible”标志更改为true。
  6. 它执行b = 2 ,将b的绑定值从undefined更改为2
  7. 评估Math.random() < 0.5 ; 可以说是真的:
  8. 由于该块包含块范围的标识符,因此引擎会为该块创建一个新的LEO,并将其“外部” LEO设置为在步骤1中创建的LEO。
  9. 它将ce绑定添加到该LEO的envrec,其“可访问”标志设置为false,值设置为undefined
  10. 它执行console.log("beta")
  11. 它执行let c ,将c的绑定的“ accessible”标志设置为true
  12. 执行c = 3
  13. 它执行d = 4
  14. 它执行console.log("gamma")
  15. 它执行let e ,将e的绑定的“ accessible”标志设置为true。
  16. 执行e = 5
  17. 它执行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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM