本文深入解析 JavaScript 中 forEach() 方法的语法结构与回调函数参数。重点探讨 forEach 不支持 break、return 无效以及遍历中删除元素导致索引错乱等常见陷阱,并提供使用 for 循环、filter 及反向遍历等替代方案,帮助开发者写出更健壮的数组处理代码。
在 JavaScript 中,forEach() 是一种用于遍历数组的标准方法。它会将数组中的每个元素依次传递给指定的回调函数进行处理。需要注意的是,调用该方法时,目标数组必须包含至少一个元素,不能为空。
forEach() 语法
[ ].forEach(function(value,index,array){
//code something
});该方法会对数组中的每一个元素执行一次提供的回调函数。
var array = ['a', 'b', 'c'];
array.forEach(function(element) {
console.log(element);
});输出结果为:
a; b; c;
forEach() 方法会对数组的每个元素执行一次提供的函数。
该方法的返回值始终为 undefined。
var arr = [1,2,3,4];
arr.forEach(alert);
//等价于:
var arr = [1, 2, 3, 4];
for (var k = 0, length = arr.length; k < length; k++) {
alert(array[k]);
}在 forEach 方法中使用的 function 回调函数接收三个参数:
- 第一个参数代表当前遍历的数组元素内容。
- 第二个参数代表当前元素对应的数组索引。
- 第三个参数代表正在被遍历的整个数组对象本身。
forEach 使用的常见陷阱
1. forEach 不支持 break 语句
在使用传统的 for 循环时,我们可以利用 break 语句跳出循环。例如,当我们需要找到数组中符合特定条件的第一个元素并立即停止遍历时,这种优化手段非常有效。
然而,forEach 并不支持 break 操作。如果在回调函数中直接使用 break,程序会抛出错误。
let arr = [1, 2, 3, 4],
i = 0,
length = arr.length;
for (; i < length; i++) {
console.log(arr[i]); //1,2
if (arr[i] === 2) {
break;
};
};
arr.forEach((self,index) => {
console.log(self);
if (self === 2) {
break; //报错
};
});那么,是否能在 forEach 中实现跳出循环的效果呢?答案是肯定的,但不能使用 break,而是需要结合 try...catch 语句来手动抛出异常以终止循环。
2. forEach 中 return 无效
首先需要明确,在普通的 for 循环内部直接使用 return 会报错(除非是在函数包裹的 for 循环中)。而在 forEach 中使用 return 不会报错,但该 return 语句并不会生效,即不会终止循环或返回值。请看以下示例:
let arr = [1, 2, 3, 4];
function find(array, num) {
array.forEach((self, index) => {
if (self === num) {
return index;
};
});
};
let index = find(arr, 2);// undefined上述代码旨在查找数字 2 在数组中的索引,但由于 return 无法终止代码运行并返回值,最终结果不符合预期。
如果我们确实需要通过 return 返回某个值,正确的做法是将 return 操作放在封装的函数内部,而不是直接在 forEach 的回调中,例如:
function find(array, num) {
let _index;
array.forEach((self, index) => {
if (self === num) {
_index = index;
};
});
return _index;
};3. forEach 删除自身元素时 index 不会重置
回顾文章开头提到的问题,那段代码实际上只执行了一次,数组也无法被完全清空。这是因为 forEach 在遍历完回调函数后,会隐式地让索引 index 自增,具体表现如下:
arr.forEach((item, index) => {
arr.splice(index, 1);
console.log(1);
//这里隐性让index自增加1
index++;
});当第一次遍历结束时,数组变为 [2],而 index 已经变成了 1。此时数组的最大索引仅为 0,不满足循环条件,因此循环提前结束。
是否有办法让 forEach 在此时不跳出循环呢?当然可以,我们可以利用 ES6 的扩展运算符(Spread Operator)来解决。
[...arr].forEach((item, index) => {
arr.splice(index, 1);
console.log(1);
});通过扩展运算符重置数组 arr,可以达到不跳出循环的目的。你会发现内部逻辑确实执行了两次,但遗憾的是 index 并没有被重置,因此数组 arr 仍然无法在遍历的同时被完全清空。
在实际开发中,遍历数组并同时删除某些项的操作十分常见。因此,习惯使用 forEach 的开发者务必注意这一特性。
for 与 forEach 的区别
说到这里,for 循环与 forEach 有什么区别呢?相信大家都能列举出几点,例如:
1. for 循环可以使用 break 跳出循环,但 forEach 不能。
2. for 循环可以控制循环起点(通过初始化 i 的数字决定),而 forEach 只能默认从索引 0 开始。
3. for 循环过程中支持手动修改索引(修改 i),但 forEach 做不到(其底层控制 index 自增,开发者无法干预)。
数组遍历并删除自身
回到问题的本质,如何在遍历一个数组的同时,删除所有符合条件的项呢?
其实很简单,我们可以使用 for 循环来实现,代码如下:
let arr = [1, 2, 1],
i = 0,
length = arr.length;
for (; i < length; i++) {
// 删除数组中所有的1
if (arr[i] === 1) {
arr.splice(i, 1);
//重置i,否则i会跳一位
i--;
};
};
console.log(arr);//[2]删除符合条件的所有项,本质上就是过滤操作。
提到过滤,本能想到的方法是 filter,实现起来也很简单:
var arr1 = arr.filter((index) => {
return index !== 1;
});
console.log(arr1); //[2]这种方法代码量更少,只是需要新建一个变量来接收 filter 方法的返回值。
如果你比较“倔强”,坚持认为 forEach 因为 index 无法重置,删除数组项很困难,那么非要用 forEach 来实现这个功能也是可以的。只是我们需要让数组进行反向遍历:
arr.slice().reverse().forEach(function (item, index, arr1) {
if (item === 1) {
arr.splice(arr1.length - 1 - index, 1);
}
});
console.log(arr);//[2]当然,我们并不推荐这种做法。因为数组方法繁多,没有绝对的好坏之分,关键在于在合适的场景下使用合适的方法。这里虽然使用 forEach 达到了目的,但代码的可读性较差。
总结
以上为个人经验总结,希望能给大家一个参考,也希望大家多多支持本站。