• 前端-dom-bom/(前端-dom-bom)Js-(前端-dom-bom)JavaScript-ES-ECMAScript JS

梗概

在 JavaScript 中,forEachfor...of 都是用于遍历数据结构的语法,但它们在原理、功能和使用场景上有显著区别。

核心区别总结

特性array.forEach()for...of
支持的数据类型仅限数组(或类数组)任何可迭代对象(Array、Map、Set、String等)
执行中断能力❌ 无法中断(无 breakreturn 支持)✔️ 支持 breakcontinuereturn
性能特点回调函数调用有额外开销(稍慢)原生循环机制(更快)
异步处理❌ 回调内 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...offor避免回调函数调用开销

注意事项

避免修改原数组

forEachfor...of 中修改数组长度(如 splice)可能导致不可预期行为。

箭头函数绑定

forEach 回调中的 this 默认为 undefined(严格模式),使用普通函数可通过第二参数指定 this

[1].forEach(function(v) {
  console.log(this); // 输出 {name: "ctx"}
}, {name: "ctx"});

总结

优先用 for...of(灵活控制+高性能),当需要索引且无需中断时用 forEach;异步遍历必须用 for...of

相关笔记