使用 var 的情况
for (var i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 输出:5、5、5、5、5
为什么会这样?
- var 的函数作用域特性:
-
var 声明的变量具有函数作用域(或全局作用域)
-
整个循环中只有一个 i 变量
- 事件循环机制:
-
setTimeout 是异步函数,会被放入任务队列
-
循环是同步执行的,会立即完成
-
当循环结束时,i 的值已经变成了 5
- 执行时序:
// 执行顺序:
// 1. 循环开始,i = 0,注册第一个 setTimeout
// 2. i++,i = 1,注册第二个 setTimeout
// 3. i++,i = 2,注册第三个 setTimeout
// 4. i++,i = 3,注册第四个 setTimeout
// 5. i++,i = 4,注册第五个 setTimeout
// 6. i++,i = 5,循环条件不满足,退出循环
// 7. 现在开始执行 setTimeout 的回调函数
// 8. 所有回调函数访问的都是同一个 i,此时 i = 5
使用 let 的情况
for (let i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 输出:0、1、2、3、4
为什么 let 能解决这个问题?
- let 的块级作用域特性:
-
let 具有块级作用域
-
JavaScript 引擎会为每次循环迭代创建一个新的 i 变量
- 变量绑定机制:
// 相当于:
{
let i = 0; // 第一次迭代的 i
setTimeout(() => console.log(i), 0); // 引用第一次迭代的 i
}
{
let i = 1; // 第二次迭代的 i
setTimeout(() => console.log(i), 0); // 引用第二次迭代的 i
}
// ... 以此类推