【问题标题】:JavaScript: Difference in hoisting for let, const? [duplicate]JavaScript:let 和 const 提升的区别? [复制]
【发布时间】:2017-09-03 16:16:18
【问题描述】:

背景

我有一个函数负责生成随机数并使其可用。

"use strict";

module.exports = function(args) {
    let {
        min,
        max,
    } = args;

    let currNumber = genRandom(min, max);

    const genRandom = (min, max) => Math.floor(Math.random() * max) + min;

    const getNumber = () => currNumber;    

    return Object.freeze({
        getNumber
    });
};

问题

由于我不明白的原因,当我使用 Node.js 7.8 运行此代码时,我收到 genRandom is not defined 的错误。

但如果我将代码更改为:

let currNumber = genRandom(min, max);

const genRandom = (min, max) => Math.floor(Math.random() * max) + min;

到:

const genRandom = (min, max) => Math.floor(Math.random() * max) + min;

let currNumber = genRandom(min, max);

然后就可以了!

我不明白为什么会这样。我以为constlet 就像var 一样被吊起,但这让我相信我错了。

问题

有人可以解释一下这种行为吗?

【问题讨论】:

  • constletvar 的不同之处在于该变量在声明之前是不可访问的。
  • 所以,它们并没有被提升到函数的顶部,对吧?
  • @tsh 您可能想将其发布为答案。
  • 仅供参考,使用var 也会导致问题。该变量可能存在,因为它的定义被提升了,但它没有值,直到为其分配值的代码行在您放置它的位置运行。现在,如果您使用function genRandom() {} 定义本地函数,那么整个函数定义将被提升。所以,这与letconst 或 ES6 无关。不会提升对任何类型变量的赋值。
  • @jfriend00:它会导致 a 问题,但会导致不同的问题。 :-)

标签: javascript node.js ecmascript-6


【解决方案1】:

constletvar 的不同之处在于该变量在声明之前是不可访问的。

只需查看规范:http://www.ecma-international.org/ecma-262/6.0/#sec-let-and-const-declarations

变量是在实例化包含它们的词法环境时创建的,但在评估变量的 LexicalBinding 之前不能以任何方式访问

语法

LexicalDeclaration[In, Yield] :
LetOrConst BindingList[?In, ?Yield] ;

LetOrConst :

常量

BindingList[In, Yield] :
词法绑定[?In, ?Yield]
BindingList[?In, ?Yield] , LexicalBinding[?In, ?Yield]

LexicalBinding[In, Yield] :
BindingIdentifier[?Yield]Initializer[?In, ?Yield]opt BindingPattern[?Yield]Initializer[?In, ?Yield]

【讨论】:

    【解决方案2】:

    我认为 const 和 let 就像 var 一样被提升,但这让我相信我错了。

    不是真的,letconst 确实被提升了,或者我喜欢称之为,half-提升。 var foolet foo 之间有两个很大的区别:作用域和初始化。您已经知道范围差异。第二个是var foofoo声明初始化(使用undefined)都被提升了。对于let,只有foo声明被提升,而不是初始化foo 仅在代码的逐步执行到达let foo 语句时初始化。您不能使用(读取或写入)未初始化的标识符。不能使用标识符的这段时间称为时间死区 (TDZ)。

    即使使用var,被提升的初始化也是使用undefined 的初始化,而不是= 右侧的值:

    console.log(typeof foo); // "undefined"
    foo();                   // TypeError: foo is not a function
    var foo = () => {};

    您所做的更改,在第一次使用之前将getRandom 的声明向上移动,是正确的做法。 (或者使用函数声明,因为声明作为一个整体[包括函数的创建]被提升了。)

    我们来看看这个半吊的东西:

    let foo = "outer";
    function x()
    {
        console.log("...");
        let foo = "inner";
        console.log(foo);
    }
    x();

    letconst 具有块作用域,但我正在使用函数,因为稍后我将与 var 进行对比。)

    x 中,内部foo 直到let foo 行才能使用。 但是,你不能访问它上面的外部foo;这失败了:

    let foo = "outer";
    function x()
    {
        console.log(foo);    // ReferenceError: `foo` is not defined
        let foo = "inner";
        console.log(foo);
    }
    x();

    这是半提升:内部foo声明被提升,但变量直到let foo 语句才初始化。这意味着您根本不能在let foo 行上方使用foo(甚至不能从包含范围内)。内部foo 会在整个函数中遮盖外部foo,但在初始化之前不能使用它。这在规范中的Let and Const Declarations 中有介绍。

    这与var相反:

    var foo = "outer";
    function x()
    {
        console.log(foo);    // undefined
        var foo = "inner";
        console.log(foo);    // "inner"
    }
    x();

    这运行得很好,因为foo(带有undefined)的声明都被提升到函数的顶部。 (在提升之后,var foo = "inner"; 行变成了一个简单的赋值语句。)因此,内部的foo 始终遮蔽外部的foo,并且也可以始终访问,最初使用其默认值 (undefined),然后使用"inner"(一旦分配给它)。

    由于 TDZ 是时间性的(与时间相关),而不是空间性的(与范围内的空间或位置相关),您可以使用由 letconst(或 class)创建的标识符 在其声明之上,而不是在其声明之前。这失败了,因为getNumber 尝试访问theNumber 它被初始化之前,而它仍在 TDZ 中:

    const getNumber = () => theNumber;
    
    console.log(getNumber()); // ReferenceError
    
    let theNumber = 42;

    这是因为getNumber 访问theNumber 它被初始化之后:

    const getNumber = () => theNumber;
    
    let theNumber = 42;
    
    console.log(getNumber()); // 42

    【讨论】:

    • 写得很好。谢谢你解释半吊
    猜你喜欢
    • 2011-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-15
    • 1970-01-01
    • 2012-12-26
    • 2017-08-16
    • 2011-09-20
    相关资源
    最近更新 更多