function foo() {
var funcs = [];
for (var i = 0; i < 3; i++) { //此处使用 var i = 0
funcs[i] = function () {
console.log("funcs - " + i);
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
}
foo();
输出结果:
funcs - 3
funcs - 3
funcs - 3
function foo() {
var funcs = [];
for (let i = 0; i < 3; i++) { //此处使用 let i = 0
funcs[i] = function () {
console.log("funcs - " + i);
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
}
foo();
输出结果:
funcs - 0
funcs - 1
funcs - 2
我知道 let 的作用域被 {} 限制,而 var 的作用域等于其所在的函数的函数体,但就从这个知识点我不能解释上述情况出现的原因。我觉得其中我对 for 语法糖背后的实际代码不了解也是重要的原因,所以来请教下。感谢。
1
chashao 2020-12-27 23:28:21 +08:00 via Android
应该是去了解一下闭包的原理吧,闭包保存的是上下文,所以上下文里面的变量 var 只被创建一次,但是 let 每次循环都会重新创建新的变量(前面都是我乱写
|
2
hyuka 2020-12-27 23:32:39 +08:00 via iPhone
不就是因为你说的吗,上面的实现在输出时 i 就为 3 了;再 funcs 调用就输出了 3,接下来的循环 i 没变,一直为 3
|
3
anguiao 2020-12-28 00:04:42 +08:00 via Android
上面的 funcs[0/1/2]指向的都是同一个“i”,循环结束后就变成 3 了。后面打印的时候,因为 i 已经是 3 了,所以输出全都是 3 。
|
4
Newyorkcity OP |
5
autoxbc 2020-12-28 00:44:40 +08:00 via iPhone
for 设计的时候还没有 let,等 let 出现后 for 有了新的语义,即每个循环节互相隔离,let 初始化的变量在每个循环节有个副本,取值只对当前循环节有效
这样的设定是强行兼容 let,尤其是 let 并不在块语句中这个现实,还有 let 自增后值可以跨循环节保持也反直觉 我觉得 for 是旧时代的糟粕,函数式和迭代器才是未来,应该多用 forEach 和 for ...of |
6
anguiao 2020-12-28 00:45:29 +08:00 via Android
@Newyorkcity 因为每次循环其实都创建了一个新的“i”,所以绑定的就是每次循环时 i 的值。
|
7
Arthur2e5 2020-12-28 06:33:40 +08:00 1
> @autoxbc 这样的设定是强行兼容 let,尤其是 let 并不在块语句中这个现实
这完全(语气助词)正常,早就有语言这么做。这个 for 的行为和以下的 C99 代码行为是一致的: ```C for (int i = 0; i < 3; i++) /* blah */; ``` 觉得这个东西很奇怪只能说是 JS 程序员见怪不怪。一个 var 影响全场才叫怪。 |
9
rodrick 2020-12-28 08:18:23 +08:00 2
看一下这个 https://es6.ruanyifeng.com/#docs/let
其实明白两点就好了: 1. var 的循环公用一个 i 2. let 的循环每次都是新的 i 3. JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量 i 时,就在上一轮循环的基础上进行计算。 至于作用域问题,for 那块是一个作用域,下面的{}又是一个子作用域,互不干扰: ``` for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } ``` 这个打印的都是 "abc" 也不会报 i 已经声明的错 |
10
autoxbc 2020-12-28 08:32:50 +08:00 via iPhone
@Arthur2e5 没错,就是从 C 借来的糟粕,你读读现代 JS,根本没有人用 for,一股 C 味的 JS 最可怕了
|
11
yeship 2020-12-28 08:36:24 +08:00
由于 JavaScript 的事件循环,function 回调会在遍历结束后才执行。因为在第一个遍历中遍历 i 是通过 var 关键字声明的,所以这个值是全局作用域下的。在遍历过程中,我们通过一元操作符 ++ 来每次递增 i 的值。当 function 回调执行的时候,i 的值等于 3 。
在第二个遍历中,遍历 i 是通过 let 关键字声明的:通过 let 和 const 关键字声明的变量是拥有块级作用域(指的是任何在 {} 中的内容)。在每次的遍历过程中,i 都有一个新值,并且每个值都在循环内的作用域中。 |
12
Seanfuck 2020-12-28 08:42:44 +08:00 via iPhone
funcs[i]=function(i){……} 试试
|
13
hubqin 2020-12-28 08:46:08 +08:00 via Android 1
了解下 js 的变量提升,所有 var 声明都会被提取到文件或函数开头,上面用 var 的循环,相当于在循环前面连续声明了 3 次 i,var i=1;var i=2;var i=3; i 是个全局变量,最终内存地址中 i 指向的值是 3 。而 let 声明的变量是块级作用域的,每个 {} 都是一个块级作用域,每次 for 也是一个块级作用域,所以 for... let...循环,声明了三次 i,值分别为 1 2 3
|
14
SxqSachin 2020-12-28 09:14:47 +08:00 1
楼上这么多大佬给出的回答居然没有重复的
我在知乎上看到过一篇文章,感觉解释的比较清楚 [我用了两个月的时间才理解 let]( https://zhuanlan.zhihu.com/p/28140450) [Runtime Semantics: LabelledEvaluation]( http://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation) |
15
yaphets666 2020-12-28 09:18:59 +08:00
|
16
autoxbc 2020-12-28 09:40:38 +08:00
@yaphets666 #15 for ... of 就是现代的 for,可以在其中使用 await 达成目的
|
17
GDC 2020-12-28 09:42:10 +08:00
@yaphets666 for...of 和 for...in 都可以 await
|
18
yaphets666 2020-12-28 09:43:58 +08:00
|
19
SmallTeddy 2020-12-28 09:57:02 +08:00
我建议楼主看一下《你不知道的 JavaScript 》这本书的上卷所讲的作用域部分的知识
|
20
wuzhanggui 2020-12-28 10:13:39 +08:00
for 循环只是一个代码块(内部分为了多个),var 变量不区分代码块,let 要区分,同一个代码块中生命多次 var a 相当于给第一次 var 的 a 重新赋值而已,所以 for (var i = 0; i < 3; i++)相当于 var i = 0;for (i = 0; i < 3; i++),而 let 就不同了,相同代码块中声明名字相同的 let 变量是不允许的,所以每个代码块中的同名称变量不同。所以 for 循环中那个函数访问的也不同。
|
21
3wdddd 2020-12-28 10:31:05 +08:00
@yaphets666 这样很慢,应该用 promise.all
|
22
3wdddd 2020-12-28 10:32:24 +08:00
i 在整个循环内有多个副本,var 只有函数顶部一个
|
23
zankard 2020-12-28 10:44:07 +08:00 via iPhone
可以去了解下 js 的 Lexical Environment 。let 是 block scope,每一次 for 迭代都会生成一个新的 Lexical Environment,使得每个迭代使用的 let 变量都不一样;而 var 是 function scope,使用的都是一个变量。
|
24
AmoreLee 2020-12-28 11:00:03 +08:00
|
25
kx5d62Jn1J9MjoXP 2020-12-28 11:10:20 +08:00
var 不支持 local scope, for 循环定义的 var 是定义在函数体中的
|
26
zckevin 2020-12-28 13:03:19 +08:00
// {
// let/const x = i; // temp_x = x; // first = 1; // undefined; // outer: for (;;) { // let/const x = temp_x; // {{ if (first == 1) { // first = 0; // } else { // next; // } // flag = 1; // if (!cond) break; // }} // labels: for (; flag == 1; flag = 0, temp_x = x) { // body // } // {{ if (flag == 1) // Body used break. // break; // }} // } // } |
27
zckevin 2020-12-28 15:20:00 +08:00
《 let/const for loop 的 v8 实现》
https://zhuanlan.zhihu.com/p/340068236 |
28
Arthur2e5 2020-12-29 17:15:03 +08:00 1
@autoxbc 没有人用 for ? for 的三段形式是对过程式程序循环的最基本表达,有设置循环变量、循环条件、向下一个循环准备的内容。
最基本的 (i = 0; i < ...; i++) 当然可以用 for of 的 iterator 表达(说 for in 的建议吊起来打),但并不是所有循环都是只有一种 iterable 提供的循环方法的。即使是最基本的 Array 都有倒过来迭代的理由,换到地铁图的图论玩意儿那当然更有。 是,你可以自己写别的的 iterator 把 [Symbol.iterator] 换了。但是你自己看看那玩意是不是就等于把那三段东西又写了一遍,还得复制粘贴空白模板代码? |