Javascript 中 let 和 var 的奇怪行为

The strange behavior of let and var in Javascript

案例 1

var a  // ===> undefined
let a  // ===> SyntaxError: Identifier 'a' has already been declared

案例 2

a = 1  // ===> 1
var a  // ===> undefined
let a  // ===> undefined

为什么情况2没有抛出异常?

案例 2 没有抛出异常,因为 a = 1.

请记住,let 变量在其值被评估之前不会被初始化。

var 相比,var 使用 undefined 初始化,不同于 let 变量

当您定义一个没有 varletconst 的变量时,它会进入全局范围。试试这个:

在您选择的 JS 解释器中,执行以下操作:

>>> a = 5
>>> global

您会看到一堆数据,在最底部,您会看到 a = 5

现在,输入

>>> var a = 7
>>> global

你会看到同样的东西,但现在 a 是 7。

重启你的解释器并输入

>>> a = 5
>>> let a = 7
>>> global

你会看到 a 仍然等于 5!这给了我们一个提示,即 varlet 没有使用相同的范围。重新启动您的解释器。

>>> a = 5
>>> let a = 6
>>> var a = 7  // Syntax Error!

现在,试试这个:

>>> var b = 6
>>> var b = 7

注意到没有语法错误了吗?我们发现,如果您在全局级别重新声明全局变量,它们将不会引发语法错误。但是,您不能在全局级别重新声明已在较低级别声明的变量。 letvar 以下的范围内创建一个变量,该变量在全局范围内声明。这就是为什么当你 运行 var 两次时,没有任何反应,但是当你 运行 let 然后 var (或 let 然后 let), 你会得到一个语法错误。

但是,从其他回答可以看出,其实并不是所有的口译员都是这样的。我本地版本的 Node 与您的结果相同,而其他一些解释器会针对情况 2 抛出语法错误。

这里是 V8 开发人员。

首先值得指出的是,正则脚本执行与"console mode"之间存在差异(a.k.a."REPL mode")。前者由 JavaScript 标准指定,因此所有引擎的行为都应相同;否则就是一个错误。后者未指定;它显然应该与前者相似,但由于交互使用,事实证明有一些细微差别是有意义的。

让我们看几个具体的例子,解释一下这是怎么回事。

(1) 重新声明一个let-变量在正则脚本中是无效的。在 DevTools 控制台中,结果很烦人:

> let a = 42j  // typo: I meant "42"
< Uncaught SyntaxError: Invalid or unexpected token
> let a = 42  // Let's try that again
< Uncaught SyntaxError: 'a' has already been declared
// Aaaargh!!!

不久前 Chrome 对围绕 let 的行为进行了一些特殊更改-控制台的重新声明,这就是为什么您现在可以看到以下差异:

> let a; let a;  // One script: redeclaration error

> let b
> let b  // Separate evaluations: OK, no error.

如果您将最后两行作为常规脚本执行的一部分执行,您会得到与在 DevTools 中将两个声明放在同一行时相同的错误。

(2) var 声明与隐式全局变量:c = 1var d = 1 之间有一个小区别:前者创建一个可配置的 属性 global对象,后者创建了一个不可配置的属性,所以虽然两者几乎等价,但事后还是可以区分的。现在,在常规脚本执行中,var 声明被提升,因此如果您在隐式使用相同变量后有显式声明:

e = 1;
var e = 2;

然后这会在内部转换为:var e; e = 1; e = 2。所以在这种情况下,您无法观察到可配置性差异。

在控制台模式下,完全相同的行为是不可能的:当您执行 e = 1 时,引擎还不知道您接下来要键入 var e = 2。因此它创建 e 作为全局对象的可配置 属性。当 var e 后跟时,它什么都不做,因为 e 属性 已经存在。

(3) let 变量与全局对象属性:使用 let 声明的变量不会在全局对象上创建属性,它们在自己的范围内,遮蔽全局 属性.您可以通过以下方式观察该效果(在常规脚本和控制台中):

f = 1
let f = 2
console.log(window.f, f)  // prints "1 2"

(4) REPL 模式下重新声明的限制:当为 V8 构建放宽对正常无效重新声明限制的 REPL 模式时,团队决定(继续)禁止会改变类型的重新声明(var, let, const) 一个变量,以及consts的重新声明:

> var g
> var g        // OK
> let g        // error: already declared
> const g = 1  // error: already declared

> let h
> let h        // OK (REPL-mode special case)
> var h        // error: already declared
> const h = 1  // error: already declared

> const i = 1
> const i = 2  // error: already declared
> var i        // error: already declared
> let i        // error: already declared

(所以这里与常规脚本执行只有一个区别,请参阅上面该行的注释。)

有了这些知识,我们现在可以回到您最初的问题:

var a  // Creates a variable of `var` kind.
let a  // Redeclaration -> error

a = 1  // Creates global object property.
var a  // Does nothing, because `a` already exists.
let a  // Creates a variable of `let` kind, shadowing global property.

TL;DR:对所有内容使用 let,您的代码将表现合理。 var 具有无法修复的奇怪语义,因为它会破坏与现有网站的向后兼容性。完全忘记关键字会产生更奇怪的行为,尤其是在涉及函数时。