一、核心区别速览
特性 | for 循环 |
forEach |
map |
---|---|---|---|
返回值 | 无 | undefined |
新数组 |
中断能力 | ✅ break /continue |
❌ 需抛异常终止 | ❌ 需抛异常终止 |
原数组修改 | 可直接修改 | 可直接修改 | 不改变原数组 |
异步支持 | ✅ await 顺序执行 |
❌ await 无效(并行) |
❌ await 无效(并行) |
适用数据类型 | 数组、字符串、类数组 | 仅数组 | 仅数组 |
🔍 关键结论:
- 修改数据 :
map
纯函数式,for
/forEach
可产生副作用- 中断需求 :需提前终止循环时,
for
是唯一选择
二、性能深度解析
1. 速度对比(百万级数据测试)
方法 | 10⁴级耗时 | 10⁶级耗时 |
---|---|---|
for |
1.2ms | 8ms |
forEach |
2.5ms | 42ms |
map |
3.8ms | 49ms |
💡 性能差异根源:
- 函数调用开销 :
forEach
/map
每次迭代触发回调函数,堆栈操作消耗性能- 引擎优化机制 :V8 对
for
循环的固定结构(起始值/终止条件/步进)有深度优化,如 循环展开(Loop Unrolling)
2. 内存占用
js
// map 生成新数组,内存翻倍
const doubled = bigArray.map(x => x*2); // 10万元素 → 额外分配 800KB
三、底层原理剖析
1. forEach
/ map
的迭代器本质
js
// 简化的 polyfill 实现
Array.prototype.myForEach = function(callback) {
for (let i=0; i<this.length; i++) {
callback(this[i], i, this); // 关键:隐式迭代器调用
}
};
所有数组遍历方法最终调用
Array.prototype[@@iterator]
,但for
直接操作内存地址
2. map
的不可变性实现
js
const newArr = [];
for (let i=0; i<arr.length; i++) {
newArr[i] = transform(arr[i]); // 隐式创建副本
}
return newArr; // 返回独立内存空间
四、最佳实践场景
场景 | 推荐方法 | 示例 |
---|---|---|
大数据量遍历(>10万) | for |
日志分析、物理引擎计算 |
链式数据转换 | map +filter |
data.map(parse).filter(validate) |
DOM 批量操作 | forEach |
nodes.forEach(node => node.classList.add('active')) |
异步任务顺序执行 | for +await |
接口顺序提交、文件分片上传 |
🔥 避坑指南:
js
// 禁止在遍历中修改数组长度!
arr.forEach((_, idx) => {
if (arr[idx] === 'delete') arr.splice(idx, 1); // ❌ 索引错乱
});
// 正确做法:倒序 for 循环
for (let i=arr.length-1; i>=0; i--) {
if (arr[i] === 'delete') arr.splice(i, 1); // ✅
}
五、扩展知识
-
for...of
的定位:- 支持迭代协议对象(Array/Map/Set)
- 性能介于
for
和forEach
之间
-
函数式编程取舍:
js
// 权衡可读性与性能
smallArray.map(x => x*2); // 声明式 ✅
bigArray.reduce((sum, x) => sum + x, 0); // 改用 for 循环优化 ✅