这个问题你的第一反应是什么?
很多人都会说map不会改变原数组,而forEach可以改变原数组。❌
看完这篇文章,相信你对这个问题会有一个新的理解!
forEach
先来看看TC39 对forEach
的介绍:
注意框起来的段落:
forEach
不会直接更改被调用的对象,但调用callbackfn
时可能会更改对象。forEach
所处理的元素范围是在首次调用callbackfn
之前设置的。调用forEach
开始后追加到数组中的元素将不会被callbackfn
访问。如果数组中的现有元素被更改,传递给callbackfn
的值将是forEach
访问这些元素时的值。
给出如下示例验证:
被遍历数据:
js
const numberArray = [1, 2, 3, 4];
const objectArray = [{ age: 15 }, { name: 24 }, { name: 30 }];
- 修改原数组:
- 对于原始类型,需要通过使用
原数组+索引
的方式,这种方式无论在什么情况都是可以改变值的,但不能直接赋值 - 对于引用类型,可以直接修改其属性
- 对于原始类型,需要通过使用
js
numberArray.forEach((value, index, array) => {
array[index] *= 2;
}); //[2,4,6,8]
objectArray.forEach((value, index, array) => {
value.age += 2;
});[{ age: 17 }, { age: 26 }, { age: 32 }]
-
遍历范围 及 当前遍历的值:
- 调用次数在调用回调前就确定了
jsnumberArray.forEach((value, index) => { numberArray.push(index); }); //[1,2,3,4,0,1,2,3]
- 每次使用的
value
都是调用时的值
jsnumberArray.forEach((value, index) => { //遍历第一个元素时将第二个元素修改为-1 if (index === 0) { numberArray[1] = -1; } //遍历第二个元素时输出 if (index === 1) { console.log(value); //-1 } });
除了这些之外:
forEach
回调的返回值永远是undefined
forEach
每次的循环可以使用return
跳过
map
再来看看TC39对map
的描述:
完全一样,这里其实就已经推翻了之前所说的:map不会改变原数组,而forEach可以改变原数组。
因此上面的对于forEach
的示例,在使用map
时效果一样;
难道map
和forEach
就没有区别了?
当然不是:
- 区别一:
map
有返回值,返回的是每次调用回调的返回值组成的新数组; - 区别二:
map
的return
调用时,返回值会被收集;
深度对比
接下来将给出map
和forEach
底层算法的步骤,并标注区别;
重点关注map
的逻辑:在map执行回调之前,就会创建一个与原数组长度相等的数组,然后每次回调的返回值将加入到新数组中 ,这也是为什么map不支持跳过循环的原因。
总结
在我们了解了其工作原理和底层算法设计之后,就能总结出两者的区别了
相同
- 对于修改原数组:
map
和forEach
都可以修改原数组,因为我们可以访问到原始数组,那么对于原始类型的修改就可以利用array[index]
进行赋值。 - 对于循环的跳出
map
和forEach
都不能完全中断循环,即不存在for循环
中调用的break
的效果,但是你可以使用try catch
并在回调中手动抛出异常
就可以中断循环了。- 使用
return
就可以达到在for循环
中调用continue
的效果,- 但是对于
map
来说,一定要注意return
的值将被收集到新数组。
- 但是对于
不同
- 对于返回值:
map
存在返回值,其返回值为一个与原数组等长的新数组,且其中每个元素为每次调用回调的返回值forEach
的返回值永远是undefined
- 对于设计目的:
forEach
往往使用在仅希望遍历数组,利用数组的元素来完成某些工作map
往往是为了根据原始数组的元素生成一个新的数组