这是因为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 定义的变量被赋予 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。即使所有变量都被提升,无论您使用哪个声明器(var、let 或 const),使用 let 和 const 引用未初始化的变量都是无效的。此外,let 和 const 具有 块作用域,而不是 函数作用域。这对于其他语言来说更加清晰和正常:
function foo() {
{
var a = 3;
}
console.log(a); //logs 3
}
对比:
function foo() {
{
let a = 3;
}
console.log(a); //ReferenceError
}