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

时间:2017-01-03 03:56:39

标签: javascript reference language-lawyer

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

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

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

看似合理,但是......

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

为什么最后一个案例没有抛出ReferenceError?是否a在定义之前被引用?

3 个答案:

答案 0 :(得分:11)

这是因为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

     

[...]

     

VariableDeclaration 定义的带有 Initializer 的变量被赋予其 {{1}的值执行 Initializer {em> AssignmentExpression ,而不是创建变量的时候。(强调我的)

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

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

a

可以改写为:

var a = a || "value";

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

相反,您的第二个示例会抛出value

ReferenceError

它也可以改写为:

var a = k || "value";

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

  

但你怎么解释ReferenceError

再次从ES2015语言规范:

  

在任何VariableEnvironment的范围内,常见的 var a = "123"; var a = a || "124"; // a = "123" 可能出现在多个 BindingIdentifier 中,但这些声明集体定义只有一个变量。

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

VariableDeclaration

可以改写为:

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

再次在同一功能或全局范围内声明var a; a = "123"; a = a || "124"; 仅集体声明一次。 a被分配到a,然后再次分配给"123",因为"123"是真实的。

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

let

这将抛出let a = a || "value"; 。即使所有变量都被提升,无论您使用哪个声明者(ReferenceErrorvarlet),constlet都是无效的引用未初始化的变量。此外,constlet具有阻止范围,而非功能范围。对于其他语言,这一点更为明确和正常:

const

对战:

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

答案 1 :(得分:0)

var a = k || 'value';
var a = a || 'value';

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

答案 2 :(得分:-1)

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

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

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

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

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

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

如果您想知道参考错误和未定义

之间的区别,请添加此项

引用错误-indicate变量尚未声明,而

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