JavaScript的for循环中let
的作用范围到底是什么?
for (let i = 0; i < 3; i++) {
let i = 4;
console.log(i);
}
console.log(i);
外部console.log
引发错误:
“未捕获的引用错误:我未定义”
证明i
在块操作范围内,但是,为什么在for循环中定义的i
不会引发任何重复的定义错误?
答案 0 :(得分:3)
for
循环的主体(带有变量let
)具有两个作用域(或LexicalEnvironments):一个作用域是迭代环境 em>,它包含在let
循环声明中用for
声明的变量,并且内部作用域包含在for循环体内(在{
之后)声明的变量。规范中对此进行了描述,从13.7.4.7 Runtime Semantics: LabelledEvaluation
IterationStatement:用于(LexicalDeclaration表达式; Expression)语句
(这是for
循环,它用let
声明变量。)
评估以上内容最终会使您:
- 让bodyResult为ForBodyEvaluation(第一个表达式,第二个表达式,语句,perIterationLets,labelSet)。
请注意,“声明”可以是一个 block (可以像大多数{
循环主体那样,以}
开头并以for
结尾)-这非常重要,因为一个块会创建另一个词法环境。
13.7.4.8 Runtime Semantics: ForBodyEvaluation说:
执行? CreatePerIterationEnvironment(perIterationBindings)。
重复
b。令result为评估stmt的结果。
...
e。执行? CreatePerIterationEnvironment(perIterationBindings)。
其中CreatePerIterationEnvironment
creates an environment包含let
循环声明中用for
声明的变量:
g。对于perIterationBindings的每个元素bn,执行
i。执行! thisIterationEnvRec.CreateMutableBinding(bn,false)。
ii。让lastValue为? lastIterationEnvRec.GetBindingValue(bn,true)。
iii。执行thisIterationEnvRec.InitializeBinding(bn,lastValue)。
因此,有两个范围:一个由CreatePerIterationEnvironment
创建,另一个由stmt
是块创建。
for (let i = 0; i < 3; i++) {
let foo = 'f';
}
在这里,i
包含在外部迭代环境中,而foo
包含在内部块中,后者是一个不同的环境。如果使这两个变量名称相同,则不会引发错误,因为该块内的let <variableName>
将创建一个变量作用于该块的,并且不会尝试使用相同的变量覆盖该变量。 迭代环境中的名称。
答案 1 :(得分:2)
ECMAScript 2015具有一个特殊的子句 1 来说明for
循环在控制结构中具有用let
定义的变量时的作用。
我的理解是,是专门设计的,目的是为循环体内的变量提供单独的环境记录,以便其循环迭代中的值可以被用作调用的嵌套函数看到支持-并且在此过程中无需捕获闭包中循环变量的值。
基本上
for( let i=0; condition; ++i) {
...
}
i
的绑定设置为零; i
并将结果存储在新的环境记录中,以进行下一次循环迭代(如果执行)。将行为与大括号内变量的块作用域进行比较会产生误导-将变量的词法作用域应用于for
循环主体不需要使用大括号:
for( let i = 0; i < 2; ++i) continue;
console.log( i);
因此let
变量的主体作用域是一种语言设计的“特殊”。
1 有关规范的技术细节,请参见CertainPerformance's answer。
答案 2 :(得分:0)
用let
声明的变量在它们的作用域之外不可见(可访问),这解释了console.log
在for循环之外引起的错误。
它还解释了为什么循环运行三遍并打印4
,原因是for循环块中的i
(声明为let i = 4;
的块)与由于i
关键字附带的不可见性,因此循环标头中的let
,即,在for循环块内的i
中的let i = 4;
在for (let i = 0; ...)
,它们是不同的,因此块中的一个不会影响外部(在标题中)。