JavaScript:了解 for 循环内的 let 范围 [重复]
Posted
技术标签:
【中文标题】JavaScript:了解 for 循环内的 let 范围 [重复]【英文标题】:JavaScript: Understanding let scope inside for loop [duplicate] 【发布时间】:2020-03-28 22:00:21 【问题描述】:请考虑下面的sn-p-
for(let i = 1; i <= 5; i++)
setTimeout(function()
console.log(i);
,100);
在这种情况下,setTimeout
中的日志将根据 for 循环的每次迭代包含变量 i
的值,即日志如下
1
2
3
4
5
为此,我已经阅读了 Internet 上的解释,例如 - let
为每个循环创建一个变量声明,这是块级声明。所以基本上它在
内创建了一个范围。
但我对这个说法有点困惑。如果let
为每个循环创建一个变量声明,它不会总是按照循环初始化语句let i=1
初始化为1
吗?
此外,变量i
在循环块之外被声明、初始化和递增,即for
循环语句中的花括号。那么,在每次迭代中,是不是相同的变量i
被递增和使用? let
究竟是如何为每个循环创建一个变量声明并具有上一次迭代的值的?
【问题讨论】:
块范围的let
在这种情况下意味着i
将是超出该循环的undefined
,而对于var
,它将是它的最后一个值。
【参考方案1】:
一般说明
当您在 for
循环结构中使用 let
时,会为每次调用循环创建一个新变量 i
,其范围仅限于循环块(在环形)。
循环的第一次迭代从for
循环初始化程序(在您的示例中为i = 1
)获取其值。每次循环迭代创建的其他新 i
变量从循环的先前调用的 i
获取它们的值,而不是从 i = 1
获取它们的值,这就是为什么它们没有全部初始化为 1
。
因此,每次循环都会有一个新变量 i
与所有其他变量分开,并且每个新变量都使用前一个变量的值进行初始化,然后由 @ 中的 i++
处理987654334@ 循环声明。
对于你的 ES6 代码:
for(let i = 1; i <= 5; i++)
setTimeout(function()
console.log(i);
,100);
在 ES5 中,这将是一个本质上等价的结构。如果你真的研究了这个,它可以为你上面的 ES6 代码中实际发生的事情提供非常丰富的信息:
(function()
for (var i = 1; i <= 5; i++)
i = (function(j)
setTimeout(function()
console.log(j);
,100);
return j;
)(i);
)();
需要两个 IIFE(立即调用的函数表达式)来模拟这一点。外层隔离var i
,使其不会泄漏出for
循环,而内层则为for
循环的每次调用提供单独的范围和单独的变量。 return j
和 i = (function(j) ...)(i)
是为了展示循环的下一次迭代如何受到循环变量修改的影响。
希望这能说明 let
对于 ES6 中的 for
循环是多么有用,以及当您需要/想要此功能时它替换了多少其他代码。
现在解决您的具体问题
为此,我已经阅读了互联网上的解释,例如 - 让为每个循环创建一个变量声明,这是块级声明。所以基本上它在
内创建了一个范围
let
定义了具有块作用域的变量(不是像var
这样的函数作用域)。而且,描绘for
循环的 和
确实定义了一个范围。
此外,变量 i 在循环块之外被声明、初始化和递增,即 for 循环语句中的花括号。
嗯,不完全是。 for
循环是关于如何初始化为第一次调用循环创建的第一个 i
的指令。 let i = 1
出现在循环之外的事实确实看起来有点令人困惑,但这实际上只是一个指令,说明它何时为循环的第一次调用创建第一个 i
变量。第一个 i
变量实际上并不存在于循环范围之外。
那么,在每次迭代中,不是同一个变量 i 都被递增和利用了吗?
没有。当 ES6 遇到带有 let
定义的 for
循环时,它会为循环的每次迭代创建一个新变量。
let 究竟如何为每个循环创建一个变量声明并具有前一次迭代的值?
这不是let
做的。这是 ES6+ JS 解释器中的 for
循环逻辑。这是for
循环的特殊行为,该循环具有用let
声明的索引初始化程序。所以,这是let
和for
的组合行为,但真正的逻辑是解释器执行for
循环的方式。
修改循环变量时的特殊情况
let
在for
循环中还有一个特殊情况。如果您在循环中分配i
的值,它将更改i
的特定值,并且还将影响下一次迭代的i
值。这有点特殊,但它仍然允许您在循环中操作i
的值。
for(let i = 1; i <= 5; i++)
let j = i;
setTimeout(function()
console.log(j);
,100);
if (i === 2)
i++; // bump the loop increment to skip the 3 value
这将创建输出:
1
2
4
5
因此,这将跳过循环的 3
迭代,因为当 i === 2
时,我们将其递增到 3,然后 for
循环执行其 i++
迭代并将其提升到 4
,有效地跳过3
迭代。
【讨论】:
谢谢你,但我认为只有一件小事值得一提。您不需要专门的 IIFE 来完成此操作,只需使用内部函数:ibb.co/bgpjnvh 或者更好地清理一下:ibb.co/8BT832C 发生的情况是因为var
的作用域是函数,当每次迭代都调用一个新的内部函数时,会在内存中为内部函数创建一个新的内部变量,这就是您现在看到的原因正确的数字。
而没有内部函数的 var(没有内部函数的初始示例),setTimeout 中的回调总是获取到它在内存中的父 var i 的引用,而不是 VALUE。
而且由于引用不断更新,最后当 setTimeout 运行时,for 循环已经完成,setTimeout 吐出引用,此时引用是最后一个数字。
@Mar - 首先,你的例子有点过时了,因为你使用的是箭头函数,如果你有箭头函数,你也可以只使用 let
而没有做额外的功能。其次,您只是替换命名函数,然后调用它们而不是使用 IIFE - 这是完全相同的概念,只是代码行更多。而且,这些都不再需要了,因为我们可以使用let
。【参考方案2】:
这样的for循环:
for (let i = 1; i <= 5; i++)
setTimeout(function()
console.log(i);
, 100);
和这样做是一样的:
let i = 1;
setTimeout(function()
console.log(i);
, 100);
let i = 2;
setTimeout(function()
console.log(i);
, 100);
let i = 3;
setTimeout(function()
console.log(i);
, 100);
let i = 4;
setTimeout(function()
console.log(i);
, 100);
let i = 5;
setTimeout(function()
console.log(i);
, 100);
变量在 for 循环的范围内被声明和赋值五次,每个实例完全独立于其他实例。
【讨论】:
【参考方案3】:在 javascript 中,您可以通过 var 或 let 定义变量。定义为 let 的变量没有细化。让我们考虑一个示例代码 sn-p
<!DOCTYPE html>
<html>
<body>
<h2>JavaScript let</h2>
<p id="demo"></p>
<script>
var i = 3;
for (var i = 0; i < 10; i++)
// some statements
document.getElementById("demo").innerHTML += i;
document.getElementById("demo").innerHTML += i;
</script>
</body>
</html>
在这段代码中,输出将是 --> 012345678910 相反,循环中带有 let 的变量不会在循环外重新声明变量。
<!DOCTYPE html>
<html>
<body>
<h2>JavaScript let</h2>
<p id="demo"></p>
<script>
let i = 5;
for (let i = 0; i < 10; i++)
// some statements
document.getElementById("demo").innerHTML += i;
document.getElementById("demo").innerHTML += i;
</script>
</body>
</html>
上述代码的输出将是 --> 01234567895 这里循环外的变量保持不变,内部循环执行,循环结束后,全局变量保持不变的值5。它基本上是根据变量的范围来分隔变量。
【讨论】:
以上是关于JavaScript:了解 for 循环内的 let 范围 [重复]的主要内容,如果未能解决你的问题,请参考以下文章
如何打破递归函数内的 for 循环并在 Javascript 中返回?