前几天重看一段两年前的老脚本,发现自己当年踩的坑现在看依然很经典,顺手记一下,给同样自学的朋友提个醒。
现象很简单:用 for 循环给十个定时器分别绑定 i,期望它们依次打印 0 到 9,结果运行完一水儿全是 9。我当时盯着控制台怀疑人生,以为是定时器写错了。后来才反应过来,问题不在延迟执行,而在闭包捕获的到底是"变量"还是"值"。
在 JS 里用 var 声明的循环变量会提升到函数作用域,十个回调捕获的其实是同一个 i 的引用,等定时器真正触发时,循环早跑完了,i 自然停在终值。从某种角度看,这不算 bug,是 late binding 的必然结果,只是新手容易把它当玄学。
其实
不止 JS。Python 里在循环里写 lambda 抓循环变量,同样会全指向最后一个值;forEach 的 index 传进去看着没事,一旦包了闭包延时调用照样翻车。本质都一样:引用而非快照。
解法倒不难。JS 把 var 换成 let,块级作用域会在每一轮循环新建绑定;或者套个工厂函数立即把当前值传进去锁死;for…of 配合 let 也行。关键就一句话,让值在被捕获的那一刻就定格,别让它跟着循环继续漂移。
写这段代码时我正啃着冷掉的三明治,debug 到凌晨才想通,事后觉得这坑交的学费挺值 ( ̄▽ ̄)