闭包不制造竞争,但多个异步任务通过闭包共享同一变量引用会导致读写冲突;关键在于变量是否被多个闭包共同持有且未封装或快照。

闭包本身不制造竞争,但当多个异步任务通过闭包访问同一变量引用时,就会出现读写冲突——这不是并发控制缺失,而是作用域隔离不足。关键在于:**变量是否被多个闭包共同持有,且未做状态封装或值快照**。
看变量声明位置,判断共享基础
如果变量定义在所有闭包的共同外层函数中(比如一个工厂函数里只声明一次 let count = 0),而返回的多个函数都读写它,那它们天然共享状态。
- 危险信号:调用一次 createCounter() 得到一组 increment/reset/get,它们操作的是同一个 count
- 安全对比:连续调用两次 createCounter(),会生成两个独立的 count,互不影响
- 注意:即使用了 let,若声明在外层统一作用域(如模块顶层),仍可能被多个闭包共用
查循环与异步结合处,揪出隐性复用
这是最常出问题的场景:变量没被显式共享,但因声明方式和执行时机,多个闭包最终指向同一内存地址。
- var i 配 setTimeout 或事件监听 → 所有回调读到的是循环结束后的最终值
- DOM 批量绑定时,把索引提取到外层变量再传入回调 → 所有事件处理函数拿到同一个 i
- Go 或 Python 中直接在循环内启动 goroutine / lambda → 捕获的是变量名而非当前值
- 修复本质不是“换关键字”,而是确保每次迭代提供独立绑定:用 let i、IIFE 封装、或显式传参(如 go func(val int){…}(i))
用运行时行为验证是否真共享
不靠猜,用最小动作暴露问题:
- 让两个闭包分别执行 ++ 和 --,再读值:若结果非线性(如 1→0→1→0),说明底层变量被交叉修改
- 在各闭包内打印 Object.is(a, b):若多个闭包中访问的变量判等为 true,证明是同一引用
- 修改其中一个闭包依赖的对象属性,观察另一个闭包后续读取是否同步变化
- Chrome DevTools 中暂停执行,展开 Closure 面板,看相同变量名是否指向同一内存地址(如 counter: 0x123abc)
用闭包封装原子性,从源头防竞争
与其事后加锁,不如让每个异步上下文自带不可分割的状态:
- 在包装函数内声明 let isRunning = false,阻止同一操作重复发起
- 创建 handler 时固化 requestId、inputValue 等元数据,响应前比对是否仍为最新
- 把 token、重试次数、原始配置等中间状态放在闭包内,避免 Promise 链断裂导致上下文丢失
- 任务完成后主动将大对象引用设为 null,防止内存驻留过久











