为什么这个赋值不会引发ReferenceError?

Wil*_*niz 4 javascript reference language-lawyer

考虑三种情况,其中两个ak是不确定的:

if (a) console.log(1); // ReferenceError
Run Code Online (Sandbox Code Playgroud)

var a = k || "value"; // ReferenceError
Run Code Online (Sandbox Code Playgroud)

似乎合理,但......

var a = a || "value"; // "value"
Run Code Online (Sandbox Code Playgroud)

为什么最后一个案子不扔ReferenceErrora在定义之前没有被引用?

Li3*_*357 11

这是因为其中一个var称为" 吊装 "的"特征" .根据链接:

因为在执行任何代码之前处理变量声明(以及一般的声明),所以在代码中的任何地方声明变量等同于在顶部声明它.这也意味着变量可以在声明之前使用.此行为称为"提升",因为看起来变量声明被移动到函数或全局代码的顶部.(强调我的)

所以,例如:

console.log(a);
var a = "foo";
Run Code Online (Sandbox Code Playgroud)

而不是ReferenceError按照您的预期抛出,因为a它在定义之前被引用,它会记录undefined.这是因为,如前所述,声明首先处理,基本上发生在顶部,这意味着它与以下内容相同:

var a;
console.log(a);
a = "foo";
Run Code Online (Sandbox Code Playgroud)

对于前面提到的功能也是如此:

function foo() {
    console.log(a);
    var a = "foo";
}
Run Code Online (Sandbox Code Playgroud)

这与以下相同:

function foo() {
    var a;
    console.log(a);
    a = "foo";
}
Run Code Online (Sandbox Code Playgroud)

要了解原因,请查看ECMAScript 2015语言规范:

13.3.2变量声明

注意

一个var声明宣称的作用域变量运行执行上下文的VariableEnvironment.Var变量在实例化包含词法环境undefined时创建,并在创建时初始化.

[...]

由定义的变量VariableDeclarationInitializer被分配了其值InitializerAssignmentExpression所述时VariableDeclaration被执行,而不是在创建变量时.(强调我的)

从这里,我们可以收集var声明是在任何代码执行之前(在它们的词法环境中)创建的,并且范围限定为包含VariableEnvironment,它可以在全局范围内,也可以在函数中.它们最初被赋予价值undefined.下一部分解释了var赋值的值是执行声明时右侧的值,而不是创建变量时的值.

这适用于您的情况,因为在您的示例中,a引用就像在示例中一样.使用前面的信息,您的代码:

var a = a || "value";
Run Code Online (Sandbox Code Playgroud)

可以改写为:

var a;
a = a || "value";
Run Code Online (Sandbox Code Playgroud)

请记住,在执行任何代码之前都会处理所有声明.JavaScript引擎看到有一个声明,变量a并在当前函数或全局范围的顶部声明它.然后给出它的价值undefined.由于undefined是假的,a被分配给value.

相比之下,你的第二个例子抛出ReferenceError:

var a = k || "value";
Run Code Online (Sandbox Code Playgroud)

它也可以改写为:

var a;
a = k || "value";
Run Code Online (Sandbox Code Playgroud)

现在你看到了问题.由于k从不在任何地方声明,因此不存在具有该标识符的变量.这意味着,与a第一个示例中的不同,k永远不会声明并抛出ReferenceError它,因为它在声明之前被引用.

但你怎么解释var a = "123"; var a = a || "124"; // a = "123"

再次从ES2015语言规范:

在任何VariableEnvironment的范围内,公共BindingIdentifier可能出现在多个变量环境中,VariableDeclaration但这些声明集合仅定义一个变量.

说白了:变量可以在同一个函数或全局范围内多次定义,但始终定义相同的变量.因此,在您的示例中:

var a = "123";
var a = a || "124";
Run Code Online (Sandbox Code Playgroud)

它可以改写为:

var a;
a = "123";
a = a || "124";
Run Code Online (Sandbox Code Playgroud)

声明a 在同一个函数或全球范围内再次集体宣布只有一次.a分配给"123",然后"123"再次分配,因为"123"是真的.


从ES2015开始,在我看来,你应该不再使用了var.它具有功能范围,可能会导致意外的分配,如问题中提到的那些.相反,如果您仍想要可变性,请尝试使用let:

let a = a || "value";
Run Code Online (Sandbox Code Playgroud)

这将抛出一个ReferenceError.即使所有的变量都悬挂,无论哪个声明符使用(var,let,或const),与letconst它是无效引用一个未初始化的变量.此外,letconst块范围,不发挥作用的范围.对于其他语言,这一点更为明确和正常:

function foo() {
    {
        var a = 3;
    }
    console.log(a); //logs 3
}
Run Code Online (Sandbox Code Playgroud)

与:

function foo() {
    {
        let a = 3;
    }
    console.log(a); //ReferenceError
}
Run Code Online (Sandbox Code Playgroud)