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
变量
当您定义一个没有 var
、let
或 const
的变量时,它会进入全局范围。试试这个:
在您选择的 JS 解释器中,执行以下操作:
>>> a = 5
>>> global
您会看到一堆数据,在最底部,您会看到 a = 5
。
现在,输入
>>> var a = 7
>>> global
你会看到同样的东西,但现在 a 是 7。
重启你的解释器并输入
>>> a = 5
>>> let a = 7
>>> global
你会看到 a
仍然等于 5!这给了我们一个提示,即 var
和 let
没有使用相同的范围。重新启动您的解释器。
>>> a = 5
>>> let a = 6
>>> var a = 7 // Syntax Error!
现在,试试这个:
>>> var b = 6
>>> var b = 7
注意到没有语法错误了吗?我们发现,如果您在全局级别重新声明全局变量,它们将不会引发语法错误。但是,您不能在全局级别重新声明已在较低级别声明的变量。 let
在 var
以下的范围内创建一个变量,该变量在全局范围内声明。这就是为什么当你 运行 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 = 1
和 var 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
具有无法修复的奇怪语义,因为它会破坏与现有网站的向后兼容性。完全忘记关键字会产生更奇怪的行为,尤其是在涉及函数时。
案例 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
变量
当您定义一个没有 var
、let
或 const
的变量时,它会进入全局范围。试试这个:
在您选择的 JS 解释器中,执行以下操作:
>>> a = 5
>>> global
您会看到一堆数据,在最底部,您会看到 a = 5
。
现在,输入
>>> var a = 7
>>> global
你会看到同样的东西,但现在 a 是 7。
重启你的解释器并输入
>>> a = 5
>>> let a = 7
>>> global
你会看到 a
仍然等于 5!这给了我们一个提示,即 var
和 let
没有使用相同的范围。重新启动您的解释器。
>>> a = 5
>>> let a = 6
>>> var a = 7 // Syntax Error!
现在,试试这个:
>>> var b = 6
>>> var b = 7
注意到没有语法错误了吗?我们发现,如果您在全局级别重新声明全局变量,它们将不会引发语法错误。但是,您不能在全局级别重新声明已在较低级别声明的变量。 let
在 var
以下的范围内创建一个变量,该变量在全局范围内声明。这就是为什么当你 运行 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 = 1
和 var 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
具有无法修复的奇怪语义,因为它会破坏与现有网站的向后兼容性。完全忘记关键字会产生更奇怪的行为,尤其是在涉及函数时。