因为在循环中创建的函数关闭创建时处于活动状态的lexical environment。该词法环境(在概念上)是一个对象,它包含在其中定义的局部变量(和其他一些东西),包括i 变量,在这种情况下,是为循环体的特定迭代创建的变量(因为非常特殊方式for 在其初始化程序中处理let 声明)。这就是“闭包”的概念,它是 JavaScript 的核心技术之一。即使执行超出了与给定词法环境相关联的范围(函数返回,我们继续循环的下一次迭代等),如果任何东西仍然具有对该环境对象的引用,就像它的所有对象一样继续存在。
由于for 在其初始化程序中处理let 的方式,funcs 中的每个条目都有自己的词法环境,因此有自己的i 副本。
当您调用其中一个函数时,会创建一个新的环境对象,并将其“外部”环境设置为附加到该函数的环境。当您在函数代码中引用 i 时,它会首先查看函数的环境,如果在其中找不到 i,它会查看外部环境——它找到它(在这种情况下)并使用它。
在你说过的评论中
如果你使用'var'而不是'let',它总是返回'3'
完全正确。使用var,i 将被提升到与for 循环所在的函数关联的环境对象(或全局对象,如果这是全局代码)。所以在循环中创建的所有函数都共享同一个i,当你调用它们时,它的值是3。
这是let/const 和var 之间的本质区别之一:let 和const 具有块作用域,而for 在其初始化程序中对let 具有特殊处理。
让我们在创建这些不同的环境对象时进行跟踪。假设这段代码:
funtion example() {
const funcs = [];
for (let i = 0; i < 3; ++i) {
funcs[i] = function() {
return i;
};
}
funcs[1](); // 1
}
example();
当我们调用example 时,在const funcs = [] 之后但在for 循环开始之前,当前环境对象是为调用example 而创建的对象,所以我们在内存中有这样的东西(忽略一些细节):
+−−−−−−−−−−−−−−−−−−+
当前环境>−−−−−−−−−−−−−−−−−−−−−−−−−−>| `example` 调用 |
|环境对象 |
+−−−−−−−−−−−−−−−−−−+
| [[Outer]] |>−−−>(创建`example`时的环境对象。)
|函数 |>−+
+−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+
+−>| (数组) |
+−−−−−−−−−−−−−+
|长度:0 |
+−−−−−−−−−−−−−+
现在,for 循环开始:创建一个新的每次迭代环境对象作为“当前”环境对象,前一个环境对象作为其“外部”环境。一个i 变量在该新的每次迭代环境对象中创建,并赋予值0:
+−−−−−−−−−−−−−−−+
当前环境>−−>|迭代 0 |
|环境对象 |
+−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−+
| [[外]] |>−−−−−−−>| `example` 调用 |
|我:0 | |环境对象 |
+−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−+
| [[Outer]] |>−−−>(创建`example`时的环境对象。)
|函数 |>−+
+−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+
+−>| (数组) |
+−−−−−−−−−−−−−+
|长度:0 |
+−−−−−−−−−−−−−+
在循环迭代期间,我们创建一个函数并将其存储在funcs 中。该函数获取对当前环境对象的引用,它保留为[[Environment]](这是一个实现的东西,你实际上不会发现,如果你看这个函数,它在代码级别是不可访问的,就在JavaScript 引擎):
+−−−−−−−−−−−−−−−+
当前环境>−+>|迭代 0 |
/ |环境对象 | +−−−−−−−−−−−−−−−−−−+
/ +−−−−−−−−−−−−−−−+ | `example` 调用 |
+ | [[外]] |>−−−−−−−−>|环境对象 |
| |我:0 | +−−−−−−−−−−−−−−−−−−+
| +−−−−−−−−−−−−−−−+ | [[Outer]] |>−−−>(创建`example`时的环境对象。)
| |函数 |>−+
| +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+
| +−>| (数组) |
| +−−−−−−−−−−−−−+
| |长度:
1 |
+−−−−−−−−−−−−−−−−−−+
| | 0 |>−−−−−>|功能 0 |
| +−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−+
| | [[环境]] |>−−−−−−+
| +−−−−−−−−−−−−−−−−−+ |
| |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- −+
现在这是在 for 初始化程序中巧妙处理 let 的地方(事实上,在 for 循环体中巧妙处理 let 和 const 也是如此):A 下一次迭代的>new环境对象被创建,i的值复制从上一次迭代的i到@ 987654360@ 进行下一次迭代。所以我们有:
+−−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−−->|迭代 0 |
| |环境对象 | +−−−−−−−−−−−−−−−−−−+
| +−−−−−−−−−−−−−−−+ | `example` 调用 |
| | [[外]] |>−−−+−−−>|环境对象 |
| |我:0 |
/ +−−−−−−−−−−−−−−−−−+
| +−−−−−−−−−−−−−−−+
+ | [[Outer]] |>−−−>(创建`example`时的环境对象。)
|
| |功能 |>−−−+
|
| +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+
|
| +−−>| (数组) |
|
+−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−+
|
当前环境>−+>|迭代 1 | | |长度:1 | +−−−−−−−−−−−−−−−−−−−+
| |环境对象 | | | 0 |>--->|功能 0 |
|
+−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−+ +−−−−−−−−−−−−− −−−−−+
| | [[外]] |>−+ | [[环境]] |>−+
| | i: 0 | +−−−−−−−−−−−−−−−−−−+ |
|
+−−−−−−−−−−−−−−−+ |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- −−−−−−−−−−+
然后,新环境中的i 递增为1,并创建一个新函数并将其存储在funcs 中,给我们:
+−−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−−->|迭代 0 |
| |环境对象 | +−−−−−−−−−−−−−−−−−−+
| +−−−−−−−−−−−−−−−+ | `example` 调用 |
| | [[外]] |>−−−+−−−>|环境对象 |
| |我:0 | / +−−−−−−−−−−−−−−−−−−+
| +−−−−−−−−−−−−−−−+ + | [[Outer]] |>−−−>(创建`example`时的环境对象。)
| | |功能 |>−−−+
| | +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+
| | +−−>| (数组) |
| +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+
|当前环境>−+>|迭代 1 | | |长度:
2 | +−−−−−−−−−−−−−−−−−−−+
|
/ |环境对象 | | | 0 |>--->|功能 0 |
|
/ +−−−−−−−−−−−−−−−+ | | 1 |>−+ +−−−−−−−−−−−−−−−−−−−+
|
+ | [[外]] |>−+ +−−−−−−−−−−−−+
| | [[环境]] |>−−−+
|
| |我:
1 |
| +−−−−−−−−−−−−−−−−−−+ |
|
| +−−−−−−−−−−−−−−−+
| |
|
| | +−−−−−−−−−−−−−−−−−−+ |
|
| +−>|功能一 | |
|
| +−−−−−−−−−−−−−−−−−−+ |
|
| | [[环境]] |>−+ |
|
| +−−−−−−−−−−−−−−−−−−+ | |
|
| | |
|
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− --------------------------------------------------------------+ |
| |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- −−−−−−−−−−−−−+
然后在该迭代结束时,我们再次为最后一次迭代做整个事情,并得到:
+−−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−−->|迭代 0 |
| |环境对象 | +−−−−−−−−−−−−−−−−−−+
| +−−−−−−−−−−−−−−−+ | `example` 调用 |
| | [[外]] |>−−+−−+ −>|环境对象 |
| |我:0 | / / +−−−−−−−−−−−−−−−−−−+
| +−−−−−−−−−−−−−−−+ | | | [[Outer]] |>−−−>(创建`example`时的环境对象。)
| | | |功能 |>−−−+
| | | +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+
| | | +−−>| (数组) |
| +−−−−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−+
| +−−−−−−−−−−−−−−−−>|迭代 1 | | | |长度:3 | +−−−−−−−−−−−−−−−−−−−+
| | |环境对象 | | | | 0 |>−−−−−>|功能 0 |
| | +−−−−−−−−−−−−−−−+ | | | 1 |>−−−+ +−−−−−−−−−−−−−−−−−−−+
| | | [[外]] |>−+ | | 2 |>-+ | | [[环境]] |>−−−−−−+
| | |我: 1 | | +−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−−−−+ |
| | +−−−−−−−−−−−−−−+ | | | |
| | | | | +−−−−−−−−−−−−−−−−−−+ |
| | +−−−−−−−−−−−−−−−+ | | +−>|功能 1 | |
| | 当前环境>−+>|迭代 2 | | | +−−−−−−−−−−−−−−−−−−+ |
| | / |环境对象 | | | | [[环境]] |>−−−+ |
| | + +−−−−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−−− -+ | |
| | | | [[外]] |>−−−+ | | |
| | | |我:2 |
| +−−−−−−−−−−−−−−−−−−+ | |
| | | +−−−−−−−−−−−−−−−+
+−−−>|功能二 | | |
| |
| +−−−−−−−−−−−−−−−−−−+ | |
| |
| | [[环境]] |>−+ | |
| |
| +−−−−−−−−−−−−−−−−−−+ | | |
| |
| | | |
| |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ----------------------------------------------------------------+ | |
| | | |
| +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- −−−−−−−−−−−−+ |
| |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- ----------------+
当我们调用funcs[1]() 时,会创建一个调用环境,并将其[[Outer]] 环境设置为函数的[[Environment]]。所以就在我们 return i 在那个函数中之前,我们有(省略一些细节):
+−−−−−−−−−−−−−−−+
当前环境>−−>|致电 |
| `funcs[1]()` |
|环境对象 |
+−−−−−−−−−−−−−−−+
| [[外]] |>−−+
+−−−−−−−−−−−−−−−+ |
|
| +−−−−−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−−−−−−+ | `示例`调用 |
| +−−−>|环境对象 |
| | +−−−−−−−−−−−−−−−−−−+
| | | [[Outer]] |>−−−>(创建`example`时的环境对象。)
| | |函数 |>−+
| | +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+
| | +−>| (数组) |
\ +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+
+−−−−−−−−−−−−+−−−>|迭代 1 | | |长度:3 | +−−−−−−−−−−−−−−−−−−−+
| |环境对象 | | | 0 |>−−−−−>| (函数) |
| +−−−−−−−−−−−−−−−+ | | 1 |>−−−+ +−−−−−−−−−−−−−−−−−−−+
| | [[外]] |>−+ | 2 |>-+ | | [[环境]] |>...
| |我: 1 | +−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−−−−−+
| +−−−−−−−−−−−−−−−+ | |
| | | +−−−−−−−−−−−−−−−−−−−+
| | +−>| (函数) |
| | +−−−−−−−−−−−−−−−−−−−+
| | | [[环境]] |>−−−−−+
| | +−−−−−−−−−−−−−−−−−−+ |
| | |
| | +−−−−−−−−−−−−−−−−−−+ |
| +−−−>| (函数) | |
| +−−−−−−−−−−−−−−−−−−+ |
| | [[环境]] |>... |
| +−−−−−−−−−−−−−−−−−−+ |
| |
| |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- −−−−−−−−+
当函数查找i 时,它会查找当前环境对象。由于它不存在,因此它查找 [[Outer]] 对象。它在那里找到它,其值为1,所以它使用的就是这个值。
相反,如果我们使用var,则i 被提升到环境对象以调用example(其中funcs 是),所以在循环之后我们有这个(注意@987654375 @ 不再在每次迭代环境中):
+−−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−−->|迭代 0 |
| |环境对象 | +−−−−−−−−−−−−−−−−−−+
| +−−−−−−−−−−−−−−−+ | `example` 调用 |
| | [[外]] |>−−+−−+−>|环境对象 |
| +−−−−−−−−−−−−−−−+ / / +−−−−−−−−−−−−−−−−−−+
| | | | [[Outer]] |>−−−>(创建`example`时的环境对象。)
| | | |我: 3 |
| | | |功能 |>−−−+
| | | +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+
| | | +−−>| (数组) |
| +−−−−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−+
| +−−−−−−−−−−−−−−−−>|迭代 1 | | | |长度:3 | +−−−−−−−−−−−−−−−−−−−+
| | |环境对象 | | | | 0 |>−−−−−>|功能 0 |
| | +−−−−−−−−−−−−−−−+ | | | 1 |>−−−+ +−−−−−−−−−−−−−−−−−−−+
| | | [[外]] |>−+ | | 2 |>-+ | | [[环境]] |>−−−−−−+
| | +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−−−−+ |
| | | | | |
| | | | | +−−−−−−−−−−−−−−−−−−+ |
| | +−−−−−−−−−−−−−−−+ | | +−>|功能 1 | |
| |当前环境>−+>|迭代 2 | | | +−−−−−−−−−−−−−−−−−−+ |
| | / |环境对象 | | | | [[环境]] |>−−−+ |
| | + +−−−−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−−−−+ | |
| | | | [[外]] |>−−−+ | | |
| | | +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−−−+ | |
| | | +−−−>|功能 2 | | |
| | | +−−−−−−−−−−−−−−−−−−+ | |
| | | | [[环境]] |>−+ | |
| | | +−−−−−−−−−−−−−−−−−−+ | | |
| | | | | |
| | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ------------------------------------------------+ | |
| | | |
| +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- −−−−−−−−−−−−+ |
| |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- ----------------+
这意味着当我们调用funcs[1]() 时,会为其创建一个新环境,它的[[Outer]] 设置为函数的[[Environment]],而在return i 之前我们有:
+−−−−−−−−−−−−−−−+
当前环境>−−>|致电 |
| `funcs[1]()` |
|环境对象 |
+−−−−−−−−−−−−−−−+
| [[外]] |>−−+
+−−−−−−−−−−−−−−−+ |
|
|
+−−−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−+
| | `示例`调用 |
| +−−−>|环境对象 |
| | +−−−−−−−−−−−−−−−−−−+
| | | [[Outer]] |>−−−>(创建`example`时的环境对象。)
| | |我: 3 |
| | |函数 |>−+
| | +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+
| | +−>| (数组) |
\ +−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−+
+−−−−−−−−−−−−+−−−>|迭代 1 | | |长度:3 | +−−−−−−−−−−−−−−−−−−−+
| |环境对象 | | | 0 |>−−−−−>| (函数) |
| +−−−−−−−−−−−−−−−+>−+ | 1 |>−−−+ +−−−−−−−−−−−−−−−−−−−+
| | [[外]] | | 2 |>-+ | | [[环境]] |>...
| +−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−−−−−+
| | |
| | | +−−−−−−−−−−−−−−−−−−−+
| | +−>| (函数) |
| | +−−−−−−−−−−−−−−−−−−−+
| | | [[环境]] |>−−−−−+
| | +−−−−−−−−−−−−−−−−−−+ |
| | |
| | +−−−−−−−−−−−−−−−−−−+ |
| +−−−>| (函数) | |
| +−−−−−−−−−−−−−−−−−−+ |
| | [[环境]] |>... |
| +−−−−−−−−−−−−−−−−−−+ |
| |
| |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ---------------------------------------------------- −−−−−−−−+
所以函数在查找i时,在当前环境中没有找到,在第一个[[Outer]]环境中也没有找到,但是在第二个环境中确实找到了 [[Outer]] 环境,其值为3,这就是它使用的值。