forEach与for循环区别


一 ❀ 引

先来看一段代码:

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效果一致,都是跳过当前迭代的剩余操作,继续下一次迭代。但需要注意的是,returnforEach中只能作用于当前回调函数,无法像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是函数直接返回,也就是结束该函数,要跳出循环用breakif代码段是不能用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的区别

  1. for循环可以使用break跳出循环,但forEach不能。
  2. for循环可以控制循环起点(i初始化的数字决定循环的起点),forEach只能默认从索引0开始。
  3. for循环过程中支持修改索引(修改 i),但forEach做不到(底层控制index自增,我们无法左右它)。
  4. for循环可以遍历类数组对象,而forEach只能遍历数组(或实现了Iterable接口的对象)。

五 ❀扩展 forEach与map的区别

相同点:

  1. 都是循环遍历数组中的每一项;
  2. 每次执行匿名函数都支持三个参数,参数分别为item(当前每一项),index(索引值),arr(原数组)。
  3. 匿名函数中的this都是指向window(非严格模式下),在严格模式下this会是undefined。
  4. 只能遍历数组。

不同点:

  1. map()会分配内存空间存储新数组并返回,forEach()不会返回数据;
  2. forEach()允许callback更改原始数组的元素。map()返回新的数组。

文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
  目录