你以为 setTimeout 是异步的就安全了?这题 5 个 5,面试官笑了
初看一眼觉得没毛病,执行后却一脸问号?
这是一道 JavaScript 面试经典题,但隐藏着作用域和闭包的双重陷阱。
👇 面试题目
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 1000);
}
你猜输出结果是?
❌ 不是 0, 1, 2, 3, 4
✅ 而是:
5
5
5
5
5
🤯 为什么会这样?明明是异步的!
我们得从两个知识点讲起:
🔍 1. var 的作用域特性
var是函数作用域,不是块级作用域。- 所以
for循环里的i是在同一个作用域里共享的。 - 当
setTimeout在 1 秒后执行时,for早就跑完了,i已经变成了 5。
每一个箭头函数都闭包捕获了同一个变量 i,最终输出的都是 5。
🔗 2. JavaScript 的作用域链与闭包
📌 闭包定义:
闭包是函数和其**词法作用域(定义时作用域)**之间的组合。即使函数在外部执行,它也能访问它定义时的作用域。
所以这段代码里,每个 setTimeout 捕获的是同一个 i 的引用,而不是值。
✅ 正确写法:如何输出 0~4?
我们有两种方式来修复它:
✅ 方法一:使用 let(推荐)
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 1000);
}
💡 为什么有效?
let是块级作用域。- 每次循环都会创建一个新的作用域,每个
i都是独立的。 - 每个
setTimeout闭包都捕获了对应的i值。
✅ 方法二:使用 IIFE(立即执行函数)
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(() => console.log(j), 1000);
})(i);
}
💡 原理解释:
- 利用闭包将每次循环的
i值通过参数传给一个新作用域j,从而“固定”下来。
🎯 延伸:闭包 + 异步,是 JavaScript 的大坑
结合这道题,我们掌握了几个重要知识点:
| 知识点 | 说明 |
|---|---|
| 作用域 | var 是函数作用域,let/const 是块级作用域 |
| 闭包 | 函数可以访问它定义时的作用域 |
| 作用域链 | 查找变量时按层层作用域往外找 |
| 异步执行 | setTimeout 是异步的,但它捕获的是变量引用,不是值 |
🧪 思维加餐:再来个变化题你能答对吗?
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), i * 1000);
}
猜猜它输出什么?
👉 会每秒输出一次 0 1 2 3 4,延迟时间也变成了动态的。
🚀 总结
JavaScript 中的闭包和作用域链常常和异步结合成“送命题”。
弄清楚变量的作用域和函数的定义位置,就能掌握闭包的真正威力!
💬 最后
这题你答对了吗?是否在某次面试中遇到过类似的陷阱?欢迎留言交流!