闭包捕获的是变量引用而非值,导致循环中所有闭包共享同一变量绑定,执行时输出最终值;修复需为每个闭包创建独立变量实例,如传参捕获、局部副本或函数工厂。

因为在循环中创建闭包时,它捕获的是外部变量的引用,而不是当前迭代的值。所有闭包共享同一个变量绑定,等真正执行时,那个变量早已变成循环结束后的最终值。
闭包不记“快照”,只存“指针”
Python、JavaScript、Go、C# 等语言中的闭包,本质上是持有一个对外部变量的引用(内存地址),不是复制一份当时的值。就像多个遥控器都对着同一个电视——按哪个,控制的都是那台。
比如 Python 中:
- for i in range(3): funcs.append(lambda: print(i)) —— 三个 lambda 都指向同一个 i
- 循环结束后 i == 2(Python range 是 0,1,2),所以调用每个函数都输出 2
- 即使你在循环里写了 print(i) 看到的是 0、1、2,那只是当时读取的值;闭包没“记住”那些瞬间
不同语言的表现一致,修复思路因机制而异
虽然语法不同,但底层逻辑相通:变量复用 + 延迟求值 = 终值覆盖。
- JavaScript(var):函数作用域,整个 for 共用一个 i,setTimeout 全输出 3
- JavaScript(let):块级作用域,每次迭代生成独立的 i 绑定,自然安全
- Go:range 变量复用,v 地址不变,所有 goroutine 或闭包读到的都是末尾值(如 5)
- C#:for 循环中直接捕获 i,委托列表全输出 3,需显式声明 int index = i 拷贝
关键不是“怎么写循环”,而是“怎么隔离变量”
修复的本质,是让每个闭包绑定一个**独立的变量实例**,而非共享源变量。
- 传参捕获:lambda x=i: print(x)|func(val){ println(val) }(i)
- 局部副本:let i = ...|int index = i|v := v
- 函数工厂:def make_f(x): return lambda: print(x)|func makeF(v int) func(){ return func(){ println(v) } }
- 高阶函数替代:map、forEach 的回调自带独立参数作用域,天然避坑











