【施工中】JS 闭包
说一说 JS 的闭包?(要求你从零开始介绍你对闭包的简要理解)
首先我们要搞明白 闭包 的概念,然后从。。。展开。
闭包概念
闭包可以用来在一个函数与一组“私有”变量之间建立关联关系。
让我们先从词法作用域开始。
作用域
JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。
因为 JavaScript 采用的是词法作用域,函数的作用域在函数定义的时候就决定了。
而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
……
绑定
首先我们来看一个代码例子:
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 最后输出结果:
* 3 3 3
* 0 1 2
*/
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i));
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i));
}
setTimeOut 是异步函数,回调要等到主线程同步代码执行完才会进入任务队列。且 JS 闭包捕获的不是变量值,而是变量引用(内存地址)。
for(var …)→ 所有迭代共享一个i。
- 这个
i绑定在外层函数作用域(如果在全局就是全局作用域)。- 每次
()=>console.log(i)形成闭包时,并不会复制i的值,而是记录“到外层作用域里i的引用”。- 所以整个循环过程只有一个
i,三个闭包都引用这同一个。
for(let …)→ 每次迭代都会创建一个新的块作用域,新的i。
关于异步回调函数何时执行?
从这里可以看出:是在所有同步代码执行完后,才开始执行异步队列中的函数。
ES6 对于 for 循环中 let、const 的特殊规则
javascript - Am I right about LexicalEnvironment and …
当我搞不清楚 “为何 for 循环中,let 和 var 会有如此大的差别” 时,我在 GPT 的回答中得知:
……但对于 let/const 声明的变量,会有一个额外的机制。
规范规定:当循环头里用了 let 或 const 声明时,每次迭代都会新建一个环境记录 (environment record)。
……底层发生的是:
for初始化时,创建了一个“循环环境”。- 每次进入一次迭代,JS 引擎会 克隆一份上一次迭代的环境,得到一个新的环境记录。
- 在新的环境里,
i绑定到一个新的存储槽(slot)。
- 如果是第一次迭代,存储槽里就是初始化的
0。- 如果是后续迭代,会把上一次迭代结束时的
i的值复制进来(比如 1, 2…)。这样,每次迭代的
i虽然名字相同,但其实指向不同的内存槽。这就是为什么 闭包能“记住”当时那一轮的 i。 而:
var声明不创建迭代环境,所有迭代共用一个环境记录。- 所以闭包里捕获到的
i始终是那个唯一的槽。
然后我在 mdn 关于 for 的描述文档中找到了相关资料:
for - JavaScript | MDN - Mozilla
更准确地说,
let声明是for循环特有的——如果initialization是let声明,那么每次循环体执行完毕后,都会发生以下事情:
- 使用
let声明新的变量会创建一个新的词法作用域。- 上次迭代的绑定值用于重新初始化新变量。
afterthought在新的作用域中执行。因此,在
afterthought中重新分配新变量不会影响上一次迭代的绑定。新的词法作用域会在
initialization之后、condition第一次被判定之前创建。
