题目 14 / 14

不用 let,用 IIFE 能修复这个 setTimeout 循环吗?

知识点: 闭包

立即调用一个函数并把 i 作为参数传入,会让每次迭代都拥有自己独立的参数绑定。

使用执行可视化器解题在下方逐步观察每个绑定、函数调用和控制台输出。

预测输出结果

预测输出。循环仍然用的是 var —— 把回调包在 IIFE 里会改变每个定时器打印的值吗?

for (var i = 0; i < 3; i++) {
  (function(i) {
    setTimeout(function() {
      console.log(i);
    }, 100);
  })(i);
}
查看答案与完整解析

正确输出

0
1
2

为什么会这样

每次迭代时,(function(i) {...})(i) 都会被立即调用,并传入共享循环变量当时的值,把这个值复制进一个全新的、名为 i 的参数绑定中。

setTimeout 的回调闭包捕获的是这个 IIFE 的参数 i,而不是外层循环的 i,因此三个回调各自捕获的是不同的、私有的绑定。

循环结束后定时器触发时,每个回调都读取自己独立的参数绑定,打印出调用时传入的值:先是 0,然后是 1,最后是 2。

这道题考察什么

  • IIFE 的实参会为每次调用创建一个全新绑定
  • 内部回调闭包捕获的是该参数,从而遮蔽外层的 var
  • 这是 ES6 之前用来模拟 let 按迭代作用域的经典写法

运行环境前提

可视化器把每次 IIFE 调用都建模为独立的函数调用,各自拥有自己的活动对象(Activation Object),因此三个 i 参数绑定在任何标准 JavaScript 运行时中都不会互相冲突。

规范参考

JavaScript 代码编辑器支持直接编辑或粘贴自定义代码
第 0 步 / 共 0 步
单步Space播放
按 → 单步 或 Space 自动播放
阶段 1: 编译预解析(变量提升与内存分配)
阶段 2: 代码执行(逐行赋值与调用求值)
单步执行原理解析

按键盘右方向键 “→” 进行单步调试,或按空格键 “Space” / 点击 “▶ 自动播放” 进行连续执行。

作用域链 (Scope Chain)
词法作用域链: 当前 AO → 外部 [[Scope]] → 全局 (GO)
暂无激活的作用域链
调用栈 (CALL STACK)执行上下文栈 (后进先出 LIFO)
后进先出 (LIFO) 栈帧
全局执行上下文 (GEC)
▶ 活动栈帧
堆内存空间 (HEAP & MEMORY SPACE)已分配对象 (GO, AO, FunctionObjects, ER)
ECMAScript 堆内存区
内存中暂无分配对象
控制台输出 (Console)
console.log(...) Stream
暂无输出产生