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

fcg9iug3  于 2023-02-07  发布在  Java
关注(0)|答案(5)|浏览(147)

我知道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吗?

ssgvzors

ssgvzors1#

文档中写道:
变量在其包含的词法环境被示例化时被创建**,但是在变量的LexicalBinding被求值之前不能以任何方式被访问**。由具有初始化器的LexicalBinding定义的变量在LexicalBinding被求值时被赋予其初始化器的AssignmentExpression的值,如果let声明中的LexicalBinding没有Initializer,则在计算LexicalBinding时,变量将被赋值为undefined值。
此外,var keyword
let允许您声明作用域仅限于使用它的块、语句或表达式的变量。这与var关键字不同,后者将变量定义为全局变量,或定义为整个函数的局部变量,而不考虑块作用域。
你也可以查看这篇文章由凯尔·辛普森:For and against let

kh212irz

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只是为了使在letconst之间切换更容易
同样,var也会受到TDZ的影响吗?我知道它什么时候会抛出未定义,但这是因为TDZ吗?
不,var不受TDZ的影响。它不抛出任何错误。它只是undefined,直到设置为其他。TDZ是一个ES6的东西。

eblbsuwk

eblbsuwk3#

你必须首先理解提升。它是把代码声明的初始化带到块的顶部,考虑下面的例子

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和function中访问,因为它是在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
以供进一步澄清

rekjcdws

rekjcdws4#

let在访问时抛出错误时,它被提升到顶部的目的是什么?
这样我们就可以有块作用域,这是一个很容易理解的概念,* 而不是 * 有var提升的块等效物,这是错误和误解的传统来源。
考虑这个块的内部:

{
    let a = 1;
    console.log(a);
    let b = 2;
    console.log(a, b);
    let c = 3;
    console.log(a, b, c);
}

设计师有三个主要选择:
1.拥有块作用域,但是所有声明都被提升到顶部并且可以访问(就像var在函数中);或
1.不具有块作用域,而是具有从每个letconstclass等开始的新作用域;或
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中启用数据块范围和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
1.它将abd的绑定添加到该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.它将ce的绑定添加到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)
  • 至少,这是他们在规范中所做的。事实上,他们可以做任何他们想做的事情,只要它按照规范定义的那样运行。事实上,大多数引擎会做更有效的事情,利用堆栈等。
dpiehjr4

dpiehjr45#

一个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的引用出错,这是应该的。

相关问题