【问题标题】:Why doesn't this assignment throw a ReferenceError?为什么这个赋值不会抛出 ReferenceError?
【发布时间】:2017-05-17 02:43:17
【问题描述】:

考虑三种情况,ak 都未定义:

if (a) console.log(1); // ReferenceError

var a = k || "value"; // ReferenceError

看起来很合理,但是……

var a = a || "value"; // "value"

为什么最后一个case不抛出ReferenceErrora 不是在定义之前就被引用了吗?

【问题讨论】:

  • @Deep 请再读一遍

标签: javascript reference language-lawyer


【解决方案1】:

这是因为var 的“功能”之一称为hoisting。根据链接:

因为变量声明(以及一般的声明)是在任何代码执行之前处理的,在代码中的任何位置声明变量都相当于在顶部声明它。这也意味着一个变量在它被声明之前可以被使用。这种行为称为“提升”,因为变量声明似乎被移到函数或全局代码的顶部。(强调我的)

所以,例如:

console.log(a);
var a = "foo";

不像您可能期望的那样抛出ReferenceError,因为a在定义之前被引用,它记录undefined。这是因为,如前所述,声明首先被处理,并且本质上发生在顶部,这意味着它与以下内容相同:

var a;
console.log(a);
a = "foo";

前面提到的函数也是如此:

function foo() {
    console.log(a);
    var a = "foo";
}

同理:

function foo() {
    var a;
    console.log(a);
    a = "foo";
}

要了解原因,请查看ECMAScript 2015 Language Specification

13.3.2 变量语句

注意

var 语句声明范围为the running execution context’s VariableEnvironment 的变量。 Var 变量在包含Lexical Environment 的实例化时创建,并在创建时初始化为undefined

[...]

VariableDeclarationInitializer 定义的变量被赋予 Initializer值' s AssignmentExpression 在执行 VariableDeclaration 时,而不是在创建变量时。(强调我的)

据此,我们可以得知var 声明是在任何代码执行之前创建的(在它们的词法环境中),并且其作用域是包含 VariableEnvironment,它要么在全局范围,或在函数中。它们最初被赋予值undefined。下一部分解释了 var 分配给的值是执行声明时右侧的值,而不是创建变量时的值。

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

var a = a || "value";

可以改写为:

var a;
a = a || "value";

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

相比之下,您的第二个示例抛出了ReferenceError

var a = k || "value";

也可以改写为:

var a;
a = k || "value";

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

但是你如何解释var a = "123"; var a = a || "124"; // a = "123"

再次来自 ES2015 语言规范:

在任何VariableEnvironment 的范围内,一个共同的BindingIdentifier 可能出现在多个VariableDeclaration 中,但这些声明集体只定义一个变量。 p>

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

var a = "123";
var a = a || "124";

可以改写为:

var a;
a = "123";
a = a || "124";

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


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

let a = a || "value";

这将抛出一个ReferenceError。即使所有变量都被提升,无论您使用哪个声明器(varletconst),使用 letconst 引用未初始化的变量都是无效的。此外,letconst 具有 块作用域,而不是 函数作用域。这对于其他语言来说更加清晰和正常:

function foo() {
    {
        var a = 3;
    }
    console.log(a); //logs 3
}

对比:

function foo() {
    {
        let a = 3;
    }
    console.log(a); //ReferenceError
}

【讨论】:

    【解决方案2】:
    var a = k || 'value';
    var a = a || 'value';
    

    a 在你调用var a 时被声明,但 k 不是,这就是为什么第一行是 ReferenceError 而第二行是 'value'

    【讨论】:

      【解决方案3】:
      if (a) console.log(1); // ReferenceError
      

      在这种情况下,我认为毫无疑问,因为 a 在使用之前没有在任何地方声明,所以参考错误

      var a = k || "value"; //ReferenceError
      

      同样的情况,这里没有声明 k,我们正在尝试使用它

      var a = a || "value"; //"value"
      

      现在在这种情况下,当我们尝试使用 a(r.h.s 中的 a)时,在使用它之前已经声明了时间 a,因此没有错误

      如果您想知道参考错误和未定义之间的区别,请添加此内容

      引用错误 - 表示变量尚未声明而

      undefined - 它是javascript中的特殊值,一旦声明它就分配给任何变量 undefined 表示变量已声明但未取任何值

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-09-18
        • 1970-01-01
        • 2010-11-09
        • 1970-01-01
        • 1970-01-01
        • 2016-12-18
        • 2019-12-03
        • 1970-01-01
        相关资源
        最近更新 更多