一 ❀ 引
先来看一段代码:
let arr = [1, 2];
arr.forEach((item, index) => {
arr.splice(index, 1);
console.log(11111); //输出几次?
});
console.log(arr); //?
let arr1 = [1, 2];
for (let i = 0; i < arr1.length; i++) {
arr1.splice(i, 1);
console.log(11111); //输出几次?
}
console.log(arr1); //?
请问,这段代码执行完毕后arr输出为多少?循环体内的console操作会执行几次?
二 ❀ forEach参数
forEach有的也叫增强for循环,forEach其实是for循环的一个特殊简化版。
与for循环一样,forEach也属于完整遍历数组的方法,并会对数组每项元素执行提供的回调函数,一个完整的forEach应该是这样,如下:
arr.forEach(function (self, index, arr) {}, this);
self:数组当前遍历的元素,默认从左往右依次获取数组元素。
index:数组当前元素的索引,第一个元素索引为0,依次类推。
arr:当前遍历的数组。
this:回调函数中this指向。
我们来看个简单的forEach例子,加强对于这四个参数的印象:
let arr1 = [1, 2, 3, 4];
let obj = {
a: 1,
};
arr1.forEach(function (self, index, arr) {
console.log(`当前元素'self'为${self}索引'index'为${index},属于数组'arr'${arr}`);
console.log(this);
}, obj);

可以看到,arr参数其实就是我们正在遍历的数组,而回调函数中的this指向我们提供的obj。
但若是forEach中使用了箭头函数()=>{},则this指向的不在是当前的回调函数,而是window。
let arr1 = [1, 2, 3, 4];
let obj = {
a: 1,
};
arr1.forEach((self, index, arr) => {
console.log(`当前元素'self'为${self}索引'index'为${index},属于数组'arr'${arr}`);
console.log(this);
console.log(obj);
}, obj);

forEach虽然是for循环的简化版本,但是并不是说foreach就比for更好用,forEach适用于循环次数未知,或者计算循环次数比较麻烦情况下使用效率更高,但是更为复杂的一些循环还是需要用到for循环效率更高。
三 ❀ forEach使用
1.forEach不支持break
大家都知道,在使用for循环时可以使用break跳出循环,比如我希望找到数组中符合条件的第一个元素就跳出循环,这对于优化数组遍历是非常棒的。很遗憾,forEach并不支持break操作,使用break会导致报错。
let arr = [1, 2, 3, 4],
length = arr.length;
for (let i = 0; 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能不能跳出循环呢?可以。
2.forEach结合try…catch可以跳出循环
try {
var arr = [1, 2, 3, 4];
arr.forEach(function (item, index) {
//跳出条件
if (item === 3) {
throw new Error("LoopTerminates");
}
//do something
console.log(item);
});
} catch (e) {
if (e.message !== "LoopTerminates") throw e;
}
3.forEach中使用return的特殊性
核心结论
forEach 里的 return 根本不能终止循环,也不能让函数返回值,它只能跳过当前一次循环,和 continue 效果一样。
首先需要明确:在非函数作用域中使用return会报错,而在函数中使用for循环时可以正常使用return。在forEach中使用return不会报错,但只会跳出当前回调函数的执行,不会终止整个forEach循环,也不会使包含forEach的函数返回值。
let arr = [1, 2, 3, 4];
function forFn(array, num) {
for (let i = 0; i < array.length; i++) {
if (array[i] == num) {
return i; // 直接返回索引,终止函数执行
}
}
}
function forEachFn(array, num) {
array.forEach((self, index) => {
if (self === num) {
return index; // 仅跳出当前回调,forEach继续执行
}
});
// 函数没有返回值,默认返回undefined
}
let index1 = forFn(arr, 2);
let index2 = forEachFn(arr, 2);
console.log(index1); // 1
console.log(index2); // undefined
上述代码中,forEachFn方法尝试在forEach回调中使用return返回索引,但由于return只作用于当前回调函数,无法将值传递给外层函数,也无法终止forEach循环。
如果需要在使用forEach后返回值,应将return操作放在外层函数中,例如:
let arr = [1, 2, 3, 4];
function forEachFnNew(array, num) {
let result = -1; // 初始化为-1,表示未找到
array.forEach((self, index) => {
if (self === num) {
result = index; // 记录找到的索引
}
});
return result; // 在函数末尾返回结果
}
let index3 = forEachFnNew(arr, 2);
console.log(index3); // 1
值得注意的是,forEach回调中使用return的行为与for循环中的continue类似:
- 在
for循环中,continue会跳过当前循环的剩余代码,直接进入下一次循环 - 在
forEach回调中,return会终止当前回调函数的执行,forEach会继续处理下一个数组元素,相当于跳过当前元素的后续处理
例如:
const array = [1, 2, 3, 4];
array.forEach((item, index) => {
if (item === 2) {
return; // 跳过当前元素的后续代码,继续处理下一个元素
}
console.log(item); // 当 item 为 2 时,这行不会执行
});
// 输出:1, 3, 4
这种情况下,return的作用与for循环中的continue效果一致,都是跳过当前迭代的剩余操作,继续下一次迭代。但需要注意的是,return在forEach中只能作用于当前回调函数,无法像for循环中的break那样终止整个循环。
你应该用什么替代?
✅ 想终止循环
用 for / for...of + break:
for (const item of arr) {
if (item === 2) break; // 直接终止整个循环
console.log(item);
}
✅ 想查找元素并返回
用 find() / some() / every():
const result = arr.find(item => item === 3);
// 找到就返回,直接终止循环
✅ 想跳过当前循环
可以用 forEach + return,或者直接用 continue。
一句话总结
forEach+return= 跳过本次循环(continue)- 不能终止循环、不能返回值
- 需要中断循环 → 用
for/for...of - 需要查找返回 → 用
find/some
4.forEach删除自身元素index不会被重置
还记得文章开头的问题吗,那段代码其实只会执行一次,数组也不会被删除干净。这是因为forEach在开始遍历前会先确定数组的长度和元素,即使在遍历过程中修改了原数组,也不会影响遍历的次数和索引的递增,索引会继续按照原始数组的长度进行自增。
5.函数中for循环中break和return区别是什么?
1.break:指的是跳出for循环本身,不再进行之后的循环,但可以执行for循环之外的语句。
function test() {
for (let i = 1; i <= 5; i++) {
if (i === 4) {
break;
}
console.log(i); // 分别输出 1,2, 3
}
console.log("end"); // 输出end
}
2.return:指的是跳出for循环,且不执行for循环之外的语句,直接跳出当前函数,返回return后的值。
function test() {
for (let i = 1; i <= 5; i++) {
if (i === 4) {
return false;
}
console.log(i); // 分别输出 1,2, 3
}
console.log("end"); // 未执行
}
3.continue(补充):与break语句不同,continue不会退出整个循环,而是跳过当前循环的剩余代码,直接进入下一轮循环。
function test() {
for (let i = 1; i <= 5; i++) {
if (i === 4) {
continue;
}
console.log(i); // 分别输出 1,2, 3 5
}
console.log("end"); // 输出end
}
return在if中的作用
return是函数直接返回,也就是结束该函数,要跳出循环用break,if代码段是不能用break跳出的。在一个函数内任意位置调用return,直接退出函数。
如果在if-else或者if-else-if中使用return,那么必须将这些条件语句放在函数中,否则会报错。同for循环中使用return一样,使用了return后,将返回到函数外边。
function testReturnInIf(num) {
if (num > 10) {
return "大于10";
} else if (num > 5) {
return "大于5";
} else {
return "小于等于5";
}
console.log("这行不会执行"); // return后不会执行
}
console.log(testReturnInIf(8)); // 输出 "大于5"
6.forEach循环中对item赋值的影响
在forEach循环中,对item直接赋值不会改变原数组的元素,但如果修改item的属性(如item.show),则会改变原数组元素的属性。
const arr = [
{ name: "Alice", age: 20 },
{ name: "Bob", age: 25 },
];
// 直接对item赋值,不会改变原数组
arr.forEach((item, index) => {
item = { name: "Changed", age: 30 };
});
console.log(arr);
// 输出:
// [
// { name: 'Alice', age: 20 },
// { name: 'Bob', age: 25 }
// ]
// 修改item的属性,会改变原数组
arr.forEach((item, index) => {
item.age = 30;
});
console.log(arr);
// 输出:
// [
// { name: 'Alice', age: 30 },
// { name: 'Bob', age: 30 }
// ]
这是因为:
- 直接对
item赋值只是改变了局部变量的引用,不会影响原数组 - 修改
item的属性是修改了原数组元素对象的属性,所以会影响原数组
四 ❀ for与forEach的区别
- for循环可以使用break跳出循环,但forEach不能。
- for循环可以控制循环起点(i初始化的数字决定循环的起点),forEach只能默认从索引0开始。
- for循环过程中支持修改索引(修改 i),但forEach做不到(底层控制index自增,我们无法左右它)。
- for循环可以遍历类数组对象,而forEach只能遍历数组(或实现了Iterable接口的对象)。
五 ❀扩展 forEach与map的区别
相同点:
- 都是循环遍历数组中的每一项;
- 每次执行匿名函数都支持三个参数,参数分别为item(当前每一项),index(索引值),arr(原数组)。
- 匿名函数中的this都是指向window(非严格模式下),在严格模式下this会是undefined。
- 只能遍历数组。
不同点:
- map()会分配内存空间存储新数组并返回,forEach()不会返回数据;
- forEach()允许callback更改原始数组的元素。map()返回新的数组。