用 let 声明循环变量可解决 for 循环中异步操作输出最后一个索引的问题,因其块级作用域使每次迭代的 i 独立;ES5 用 IIFE、数组场景用 forEach、遍历类数组可用 for...of + entries()。

在 JavaScript 中,用 for 循环配合异步操作(比如 setTimeout、事件监听、fetch)时,常会遇到“所有回调都输出最后一个索引”的问题。根本原因是循环变量(如 i)是共享的,而回调执行时循环早已结束,i 已变成最终值。闭包能保存每次迭代时的值,关键在于让每个回调“捕获”当次的索引。
用立即执行函数(IIFE)包裹循环体
这是 ES5 最经典解法:把当前的 i 作为参数传入一个立即执行函数,在其作用域内形成闭包。
for (var i = 0; i < 3; i++) {
(function(index) {
setTimeout(function() {
console.log(index); // 输出 0, 1, 2
}, 100);
})(i);
}
说明:
-
var声明的i是函数作用域,整个循环共用一个i; - IIFE 每次调用都创建新作用域,
index是形参,接收当次的i值,被内部函数闭包引用; - 注意括号位置:
(function(){...})(i)或(function(i){...})(i)都可,关键是立即执行并传参。
用 let 声明循环变量(推荐,ES6+)
let 具有块级作用域,每次循环都会为 i 创建一个独立绑定,无需手动闭包。
立即学习“Java免费学习笔记(深入)”;
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出 0, 1, 2
}, 100);
}
说明:
- 每次迭代的
i是不同的绑定,箭头函数或普通函数都能正确访问它; - 这是最简洁、可读性最强的方式,现代项目应优先使用;
- 注意不能用
var+let混用——for (var i...)仍会出错,必须写成for (let i...)。
用 forEach 替代 for 循环
Array.prototype.forEach 的回调函数天然具有闭包特性,每个元素的索引和值都是独立参数。
[0, 1, 2].forEach(function(item, index) {
setTimeout(() => {
console.log(index); // 输出 0, 1, 2
}, 100);
});
说明:
- 适用于已知数组的情况(如
domList.forEach(...)); - 回调的
index参数每次都是新值,自动形成闭包; - 如果原始数据不是数组,可用
Array.from({ length: n })或[...Array(n).keys()]构造。
用 for...of 配合解构或辅助变量
若需遍历类数组或带索引的结构,可结合 entries() 方法:
for (const [index, item] of [0, 1, 2].entries()) {
setTimeout(() => {
console.log(index); // 输出 0, 1, 2
}, 100);
}
说明:
-
entries()返回键值对迭代器,for...of每次解构出独立的index和item; - 本质仍是块级绑定,每个循环体都有自己的
index变量; - 比传统
for更语义化,适合遍历 Map、Set 或数组。
核心逻辑就一条:确保回调函数引用的变量,在定义时就绑定了当次迭代的值。用 let 是最直接的方式;兼容老环境时用 IIFE;已有数组优先选 forEach 或 for...of。不复杂但容易忽略细节。


















