题目 12 / 14
为什么三个 setTimeout 回调都打印 3,而不是 0、1、2?
知识点: 闭包
var 在整个循环中只创建一个共享绑定,所有回调最终都会读到同一个最终值。
使用执行可视化器解题在下方逐步观察每个绑定、函数调用和控制台输出。预测输出结果
预测输出。每次循环迭代会给回调一个独立的 i,还是三个回调共享同一个?
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}查看答案与完整解析
正确输出
3
3
3为什么会这样
var i 在整个循环期间只声明一次,而不是每次迭代各声明一次,因此传给 setTimeout 的三个回调闭包捕获的都是同一个 i 绑定。
循环会同步执行完毕,把共享的 i 递增到 3,并把三个回调都注册进定时器任务队列,此时它们都还没有机会运行。
同步代码执行完毕后,排队的回调依次运行。每个回调都在共享的外层作用域中查找 i,得到的都是同一个最终值 3。
这道题考察什么
- var 是函数作用域,而非块级作用域
- 闭包捕获的是绑定,而不是值的快照
- 排队的回调只会在同步代码结束后运行
运行环境前提
可视化器把 setTimeout 建模为宏任务,总是在当前同步代码执行完毕后才运行,这与 Node.js、浏览器以及其他标准 JavaScript 运行时一致。把 var 换成 let 会让每次迭代拥有独立绑定,输出将变为 0、1、2。