- 前端-dom-bom/(前端-dom-bom)Js-(前端-dom-bom)JavaScript-ES-ECMAScript JS
梗概
在 JavaScript 中,forEach 和 for...of 都是用于遍历数据结构的语法,但它们在原理、功能和使用场景上有显著区别。
核心区别总结
| 特性 | array.forEach() | for...of |
|---|---|---|
| 支持的数据类型 | 仅限数组(或类数组) | 任何可迭代对象(Array、Map、Set、String等) |
| 执行中断能力 | ❌ 无法中断(无 break 或 return 支持) | ✔️ 支持 break、continue、return |
| 性能特点 | 回调函数调用有额外开销(稍慢) | 原生循环机制(更快) |
| 异步处理 | ❌ 回调内 await 无效(无法顺序等待) | ✔️ 支持 await(可顺序异步) |
| 迭代索引访问 | ✔️ 回调参数自带 index | ❌ 需手动用计数器或 Array.entries() |
深入解析各维度差异
可迭代对象支持
// forEach 仅适用于数组
[1, 2, 3].forEach(v => console.log(v));
// for...of 支持多种可迭代对象
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
console.log(key, value); // 输出 'a 1', 'b 2'
}循环控制能力
// forEach 无法中断循环
[1, 2, 3].forEach(v => {
if (v === 2) return; // 仅跳出当前回调,后续元素继续执行!
console.log(v); // 输出 1, 3
});
// for...of 支持中断
for (const v of [1, 2, 3]) {
if (v === 2) break; // 完全终止循环
console.log(v); // 只输出 1
}异步处理行为(关键区别!)
// forEach 无法顺序等待异步操作
const asyncTask = (ms) => new Promise(r => setTimeout(r, ms));
[100, 200, 300].forEach(async (ms) => {
await asyncTask(ms); // ❌ 不等待:并行执行!
console.log(`Done ${ms}ms`);
});
// 控制台输出(顺序随机): "Done 100ms"、"Done 200ms"、"Done 300ms"
// for...of 支持顺序异步
for (const ms of [100, 200, 300]) {
await asyncTask(ms); // ✔️ 按顺序等待
console.log(`Done ${ms}ms`);
}
// 控制台顺序输出: "Done 100ms" → "Done 200ms" → "Done 300ms"深层机制解析
forEach 的设计机制是同步触发所有回调函数,且不等待也不处理回调函数返回的 Promise,因此无法实现异步操作的顺序控制。
这意味着:
- forEach 会立即同步调用所有回调函数
- 即使回调函数是 async 函数,forEach 也不会等待其返回的 Promise
- 所有异步操作会并行执行,而非顺序执行
参考:迭代await
性能对比
const bigArray = Array(1e6).fill(0);
console.time('forEach');
bigArray.forEach(v => v * 2); // 平均耗时 ~90ms(Chrome)
console.timeEnd('forEach');
console.time('for...of');
for (const v of bigArray) { v * 2 } // 平均耗时 ~15ms(快6倍)
console.timeEnd('for...of');选型建议
| 场景 | 推荐语法 | 理由 |
|---|---|---|
| 需要中断循环(查找、条件跳出) | for...of | 支持 break/return |
| 遍历非数组对象(Map、Set、NodeList等) | for...of | 直接支持迭代器协议 |
| 数组遍历 + 需索引位置 | array.forEach() | 回调自带 (item, index, array) 参数 |
| 异步任务需顺序执行 | for...of + await | 保证每一步等待完成 |
| 性能敏感操作(大数据处理) | for...of 或 for | 避免回调函数调用开销 |
注意事项
避免修改原数组
在 forEach 或 for...of 中修改数组长度(如 splice)可能导致不可预期行为。
箭头函数绑定
forEach 回调中的 this 默认为 undefined(严格模式),使用普通函数可通过第二参数指定 this:
[1].forEach(function(v) {
console.log(this); // 输出 {name: "ctx"}
}, {name: "ctx"});总结
优先用 for...of(灵活控制+高性能),当需要索引且无需中断时用 forEach;异步遍历必须用 for...of!