核心是让闭包绑定独立变量实例而非共享源变量;用let声明循环变量可天然解决,因每次迭代创建独立绑定;旧环境需显式创建局部副本或通过参数传值;高阶函数如forEach也天然隔离变量。

核心在于让每个闭包绑定一个独立的变量实例,而不是共享源变量。问题不在于“写了闭包”,而在于“捕获了谁、怎么捕获”。
用块级作用域变量隔离每次迭代
JavaScript 中直接用 let 声明循环变量,就能天然解决:引擎会在每次迭代中创建独立绑定,闭包捕获的是当次的 i,不是终值。
- ✅ 正确写法:
for (let i = 0; i < 5; i++) { setTimeout(() => console.log(i), 100); } - ❌ 错误写法:
for (var i = 0; i < 5; i++) { setTimeout(() => console.log(i), 100); }—— 全部输出5
显式创建局部副本,绕过语言机制差异
在不支持 per-iteration binding 的环境(如旧版 JS、Go < 1.22、C#),手动制造独立变量是最通用方案。
- Go 中写
i := i或idx := i,让闭包捕获新声明的局部变量 - C# 中写
int local = i;再捕获local - Python 中用
lambda x=i: print(x)把当前值作为默认参数固化
改用函数参数传值,切断引用依赖
把需要的数据提前取出,通过参数传入闭包,避免它去“找”外部变量。
- 不要:
funcs.push(() => doSomething(item))(item 是循环变量) - 要:
funcs.push((val) => doSomething(val))(item)或funcs.push(doSomething.bind(null, item)) - 尤其适合事件绑定、定时器、goroutine 启动等延迟执行场景
优先选用高阶函数替代原始 for 循环
forEach、map、filter 等方法的回调自带独立参数作用域,天然隔离变量。
-
[0,1,2].forEach(i => setTimeout(() => console.log(i), 100));—— 输出0 1 2 - 注意:仅适用于数组;类数组对象需先转为数组,或用
for...of+let


















