JavaScript闭包捕获外部变量的引用而非值;var循环中所有闭包共享同一i绑定致输出终值,let则为每次迭代创建独立绑定从而保留各次值。

JavaScript 中闭包捕获的是外部变量的引用,而非创建时的值;但这个“引用”在循环或异步场景中常被误认为是“实时”的——实际上它始终指向同一内存位置,而该位置的值可能已被后续代码修改。
闭包捕获的是引用,不是快照
函数定义时,并不复制外部变量的值,而是记下变量在作用域链中的位置(即绑定到词法环境中的一个绑定记录)。只要该变量未被垃圾回收,闭包就能通过引用持续访问甚至修改它。
- 普通情况:外部变量后续被重新赋值,闭包内读取会得到新值
- var 声明的变量存在变量提升,且在函数作用域内共享同一个绑定
- let/const 声明在块级作用域中为每次迭代创建独立绑定——这是解决经典循环问题的关键
for 循环中 var 与 let 的差异本质
使用 var 时,整个循环共用一个 i 变量;所有闭包都引用这同一个 i。循环结束时 i 已变为终值(如 5),因此所有函数执行时输出相同数字。
使用 let 时,每次迭代都会为 i 创建一个新的绑定(新的词法环境记录),每个闭包捕获的是各自迭代中那个独立的 i 绑定,因此能保留各次迭代时的值。
立即学习“Java免费学习笔记(深入)”;
示例对比:// var:全部输出 5
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100);
}
// let:依次输出 0 1 2 3 4
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100);
}
闭包中的变量是否“实时”,取决于修改时机和位置
闭包看到的值是否变化,不取决于“闭包是否活跃”,而取决于:外部代码是否修改了它所引用的那个变量本身。
- 如果外部函数返回后,仍有其他代码修改了该变量(比如全局变量或外层闭包暴露的 setter),那么后续调用闭包会读到新值
- 如果变量是 const 对象,对象属性被修改(如 obj.x = 10),闭包访问 obj.x 也会反映更新——因为引用没变,只是内容变了
- 但如果变量被重新赋值(如 obj = {}),则原闭包仍指向旧对象,不受影响
手动捕获“快照值”的常用方式
当确实需要固化某个时刻的值(而非响应式引用),可显式做一次值拷贝或绑定:
- 立即执行函数表达式(IIFE)传参:
(function(val) { return function() { console.log(val); }; })(i) - 箭头函数参数绑定:
arr.map((val, i) => () => console.log(val)) - 利用 Function 构造器(不推荐,性能与安全问题)
- 结构赋值解构出原始值(仅适用于基础类型或浅拷贝需求)










