块级变量重新声明与 var 与 let/const



Part 1

给定此示例:

var number = 10
{
var number = 42
}
console.log(number) // 42

为什么4号线不抛出Uncaught SyntaxError: Identifier 'number' has already been declared?由于块范围,它适用于let/const(尽管输出当然10不是42),但它为什么适用于var

第 2 部分

将此与以下适用于var的以下内容进行比较:

var number = 10
var number = 42
console.log(number) // 42

但没有let

let number = 10
let number = 42 // SyntaxError
console.log(number)

为什么var会获得"免费通行证"?它是否与使用窗口对象时重新分配给窗口对象的number属性有关var

你可以在 JavaScript 中重新声明var变量,即使在严格模式下也是如此。

使用var声明的变量的作用域是其当前执行上下文,该上下文可以是封闭函数,或者对于在任何函数外部声明的变量,是全局函数。如果你重新声明一个 JavaScript 变量,它不会丢失它的值。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#var_hoisting

'use strict'
var someVar = 'Hello';
var someVar = 2 + 2;
console.log(someVar);

为什么第 4 行不抛出Uncaught SyntaxError: Identifier 'number' has already been declared

正如 Sébastien 已经提到的,用var声明的变量在当前执行上下文中声明,并且可以重新声明。可以将执行上下文的概念与框进行比较。根据 ECMAScript 语言规范第 8.3 节:

8.3 执行上下文

执行上下文是一个规范设备,用于跟踪 ECMAScript 实现对代码的运行时评估。在任何时间点,每个代理最多有一个实际执行代码的执行上下文。这称为代理的运行执行上下文

[...]

ECMAScript 代码的执行上下文具有表 22 中列出的其他状态组件。

表 22:ECMAScript 代码执行上下文的其他状态组件
组件用途 词法环境
标识用于解析此执行上下文中代码进行的标识符引用的词法环境。 变量环境 标识其环境记录包含此执行上下文中由变量语句创建的绑定的词法环境。

因此,每次编写 JavaScript 代码时,它都会被分成称为执行上下文的小"盒子",每当解释器遇到新的语法结构(如块、函数声明等)时,就会创建这些"盒子"。在每个盒子中,都有许多组件,但尤其是LexicalEnvironmentVariableEnvironment.它们都是父执行上下文"box"内的"迷你框",其中包含对代码可以访问的当前执行上下文中声明的变量的引用。LexicalEnvironment是指用letconst声明的变量。VariableEnvironment是指使用var创建的变量。

现在看第 13.3.2 节:

13.3.2 变量语句

     注意 var 语句声明的变量作用域为正在运行的执行上下文的变量环境。Var 变量在实例化其包含词法环境时创建,并在创建时初始化为undefined在任何变量环境的范围内,公共绑定标识符可能出现在多个变量声明中,但这些声明共同只定义一个变量

引用注释的最后一部分说明了您可以多次声明var的原因。每次解释器遇到函数时,都会创建一个新的VariableEnvironmentvar因为它是函数范围的,如果你在全局范围内,则有一个全局VariableEnvironment。在您的情况下,您在全局范围内两次声明number,因为{ … }是一个块,而不是一个函数,但它们共同只定义一次number。因此,您的代码实际上执行与此相同的操作:

var number = 10 //declared once
{
number = 42 //since { … } does not create a new VariableEnvironment, number is the same 
//variable as the one outside the block. Thus, the two declarations only define
//number once and every redeclaraction is essentially reassignment.
}
console.log(number) //42

正如你所说,letconst是块范围的。它们不会抛出错误,因为{ … }会创建一个新块。

为什么var会获得"免费通行证"?它是否与使用var时将number属性重新分配给窗口对象有关?

如前所述,var声明操作可以在VariableEnvironment内多次发生 - 但对于letconst来说并非如此。正如Bergi所提到的,ECMAScript的作者直到很久以后才意识到拥有如此糟糕的声明器的失败和怪癖,var,改变var的行为会导致整个互联网崩溃,因为向后兼容性是ECMAScript/JavaScript的一个重要方面。因此,作者引入了新的声明符,letconst,旨在块范围和可预测,更像您在其他语言中看到的其他声明符。因此,只要同一范围内存在另一个声明,letconst声明就会引发错误。这与window无关,只是因为兼容性和历史问题。

最新更新