var变量在for循环与setTimeout中输出值异常的技术问询
为什么setTimeout内部的i输出为4而不是3?
这个问题是JavaScript里作用域机制和事件循环结合的经典坑,我来给你一步步理清楚原因:
核心原因拆解
先看你的代码:
var arr = [ 10, 12, 15, 21 ]; for(var i = 0; i < arr.length; i++) { setTimeout(function(){ console.log('Index: ' + i + ', element: ' + arr[i]) }, 3000) }
var的作用域缺陷:用var声明的i是函数级/全局作用域,整个循环过程中只有一个i变量,而不是每次循环都生成新的i。也就是说,四个setTimeout的回调函数,共享同一个i的引用。- 事件循环的执行顺序:
setTimeout属于宏任务,它的回调不会立刻执行,而是被放到浏览器的宏任务队列中,要等当前所有的同步代码(也就是整个for循环)执行完毕后,才会开始处理队列里的回调。 - 循环结束时的
i值:循环的终止条件是i < arr.length,arr的长度是4。当i递增到4时,4 < 4不成立,循环停止。这时候i的值已经是4了。 - 回调执行时的取值:3秒后,四个定时器回调依次执行,它们访问的都是那个已经变成4的
i变量,所以输出的Index全是4,arr[4]自然就是undefined。
几种可行的解决办法
想要让每个回调拿到对应循环次数的索引,有这些常用方案:
1. 用let声明i(ES6+推荐方案)
let支持块级作用域,每次循环都会创建一个全新的i变量,每个setTimeout回调捕获的都是当前循环的那个i:
var arr = [ 10, 12, 15, 21 ]; for(let i = 0; i < arr.length; i++) { setTimeout(function(){ console.log('Index: ' + i + ', element: ' + arr[i]) }, 3000) }
2. 用闭包创建独立作用域(ES6之前的解决方案)
通过立即执行函数表达式(IIFE)为每次循环创建私有作用域,把当前的i作为参数传进去,让回调能捕获到这个局部的索引值:
var arr = [ 10, 12, 15, 21 ]; for(var i = 0; i < arr.length; i++) { (function(currentIndex) { setTimeout(function(){ console.log('Index: ' + currentIndex + ', element: ' + arr[currentIndex]) }, 3000) })(i); }
3. 使用forEach遍历数组
forEach的回调函数本身就自带独立作用域,每次遍历都能直接拿到当前的索引和元素,写法更简洁:
var arr = [ 10, 12, 15, 21 ]; arr.forEach(function(element, index) { setTimeout(function(){ console.log('Index: ' + index + ', element: ' + element) }, 3000) });
内容的提问来源于stack exchange,提问作者Aessandro
相关产品推荐
相关产品推荐

