0
点赞
收藏
分享

微信扫一扫

关于 JavaScript 中的 forEach 循环你不知道的 8 件事

关于 JavaScript 中的 forEach 循环你不知道的 8 件事_数组

熟悉 PHP 的开发者,第一次看到使用 ​​.forEach()​​ 方法来遍历数组时,大多数认为这与标准 ​​for​​ 循环的实现完全相同。在深入学习 JavaScript 之后,很快就能意识到两者之间存在差异。本文就来介绍一下关于 forEach 循环不知道的 8 个知识点。

1、不支持处理异步函数

const test = async () => {
let arrayNumbers = [3, 2, 1];
arrayNumbers.forEach(async (item) => {
const res = await mockSync(item);
console.log(res);
});
console.log("===> 结束");
};
const mockSync = (x) =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(x);
}, 1000 * x);
});
test();

从这段代码看,期望的输出为:

3
2
1
===> 结束

实际代码运行的输出如下:

===> 结束
1
2
3

JavaScript 中的 ​​forEach()​​ 方法是一个同步方法,不支持处理异步函数。如果在 ​​forEach()​​ 中执行异步函数,​​forEach()​​ 无法等待异步函数完成,它将继续执行下一个项目。这意味着,如果在 ​​forEach()​​ 中使用异步函数,则无法保证异步任务的执行顺序。

如果要在循环中处理异步函数,则可以使用 ​​map()​​、​​filter()​​、​​reduce()​​ 和 ​​for​

​map()​​、​​filter()​​、​​reduce()​​ 三个方法支持在函数中返回 ​​Promise​​,并会等待所有 ​​Promise​​ 完成。下面使用 ​​map()​​ 和​​Promise.all()​​ 处理异步函数的示例代码如下:

const arrayNumbers = [1, 2, 3, 4, 5];
const asyncFunction = async (num) =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num * 2);
}, 1000);
});
const promises = arrayNumbers.map(async (num) => {
const result = await asyncFunction(num);
return result;
});
Promise.all(promises).then((results) => {
console.log(results); // [ 2, 4, 6, 8, 10 ]
});

上面的代码片段在 ​​async​​ 函数中使用了 ​​await​​ 关键字,​​map()​​ 方法会等待 ​​async​​ 函数完成并返回结果,以便正确处理 ​​async​​ 函数。

  • ​​浅谈JavaScript中的forEach和map之间的区别​​

下面再用 ​​for​​ 来实现,可以达到预期效果:

const arrayNumbers = [1, 2, 3, 4, 5];
const asyncFunction = async (num) =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num * 2);
}, 1000);
});
const processArray = async (arr) => {
const results = [];
for (let i = 0, len = arr.length; i < len; i++) {
const result = await asyncFunction(arr[i]);
results.push(result);
}
console.log(results); // [ 2, 4, 6, 8, 10 ]
};
processArray(arrayNumbers);

2、无法捕获异步函数中的错误

如果异步函数在执行时抛出错误,使用 ​​forEach()​​ 是无法捕获该错误。这意味着即使 ​​async​​ 函数发生错误,​​forEach()​​ 也会继续执行。

3、除了抛出异常之外,没有办法中止或跳出 forEach() 循环

​forEach()​​ 方法不支持使用 ​​break​​ 或 ​​continue​​ 语句来中断循环或跳过项目。如果需要跳出循环或跳过某个项目,则应使用 ​​for​​ 循环或其他支持 ​​break​​ 或 ​​continue​​ 语句的方法。

下面是通过抛出异常方式退出循环:

const forEachExist = (array, callback, conditionFn) => {
try {
array.forEach((item) => {
if (conditionFn(item)) {
throw new Error("ExitLoop");
}
callback(item);
});
} catch (e) {
if (e.message !== "ExitLoop") {
throw e;
}
}
};
const arrayNumbers = [1, 2, 3, 4, 5, 6];
forEachExist(
arrayNumbers,
(item) => console.log(item),
(item) => item === 3
); // 输出:1 2
const arrayObjects = [
{
title: "文章1",
},
{ title: "文章2" },
];

forEachExist(
arrayObjects,
(item) => console.log(item),
(item) => item.title === "文章2"
); // { title: '文章1' }

4、forEach 删除自己的元素,索引无法重置

在 ​​forEach()​​ 中,无法控制 ​​index​​ 的值,它会无意识地增加,直到大于数组长度,跳出循环。因此,也不可能通过删除自身来重置索引。来看一个简单的例子:

let arrayNumbers = [1, 2, 3, 4];
arrayNumbers.forEach((item, index) => {
console.log(item); // 1 2 3 4
index++;
});

5、this 指向问题

在 ​​forEach()​​ 方法中,​​this​​ 关键字指向调用该方法的对象。然而,当使用普通函数或箭头函数作为参数时,​​this​​ 关键字的作用域可能会导致问题。在箭头函数中,​​this​​ 关键字指向定义函数的对象。在普通函数中,​​this​​ 关键字指向调用函数的对象。如果需要确保 ​​this​​ 关键字的作用域是正确的,可以使用 ​​bind()​​ 方法来绑定函数的作用域。下面是 ​​forEach()​​ 方法中 ​​this​​ 关键字作用域的问题示例:

const obj = {
name: "QuintionTang",
friends: ["Doman", "Raymon", "Dave"],
printFriends: function () {
this.friends.forEach(function (friend) {
console.log(`${this.name}是${friend}的朋友`);
});
},
};
obj.printFriends();

在这个例子中,定义了一个名为 ​​obj​​ 的对象,它有一个 ​​printFriends()​​ 方法。在 ​​printFriends()​​ 方法中,使用 ​​forEach()​​ 方法遍历 ​​friends​​ 数组,并使用普通函数打印每个朋友的名字和 ​​obj​​ 对象的 ​​name​​ 属性。运行代码输出如下:

undefined是Doman的朋友
undefined是Raymon的朋友
undefined是Dave的朋友

这是因为,在 ​​forEach()​​ 方法中使用普通函数时,函数的作用域不是调用 ​​printFriends()​​方法的对象,而是全局作用域。因此,无法在该函数中访问 ​​obj​​ 对象的属性。

要解决这个问题,可以使用 ​​bind()​​ 方法绑定函数作用域,或者使用箭头函数定义回调函数。

下面将使用箭头函数定义回调函数,则运行就达到预期了,如下:

const obj = {
name: "QuintionTang",
friends: ["Doman", "Raymon", "Dave"],
printFriends: function () {
this.friends.forEach((friend) => {
console.log(`${this.name}是${friend}的朋友`);
});
},
};
obj.printFriends();

代码输出结果如下:

QuintionTang是Doman的朋友
QuintionTang是Raymon的朋友
QuintionTang是Dave的朋友

还可以使用 ​​bind()​​ 方法解决问题的代码示例:

const obj = {
name: "QuintionTang",
friends: ["Doman", "Raymon", "Dave"],
printFriends: function () {
this.friends.forEach(
function (friend) {
console.log(`${this.name}是${friend}的朋友`);
}.bind(this)
);
},
};
obj.printFriends();

上面的代码通过使用 ​​bind()​​ 方法绑定函数作用域,可以正确访问 ​​obj​​ 对象的属性。

6、forEach 的性能低于 for 循环

  • ​for​​:​​for​​ 循环没有额外的函数调用栈和上下文,所以它的实现是最简单的。
  • ​forEach()​​:对于 ​​forEach​​,其函数签名包含参数和上下文,因此性能会低于 ​​for​​ 循环。
  • ​for...of​​:支持循环体中的各种控制流,如 ​​continue​​、​​break​​、​​yield​​ 和 ​​await​​。在效率上,​​for...of​​ 比 ​​forEach()​​ 快。

 

  • ​​JavaScript 中的 .forEach() 和 for...of​​​

7、将跳过已删除或未初始化的项目

const array = [1, 2 /* empty */, , 4];
let num = 0;
array.forEach((ele) => {
console.log(ele);
num++;
});
// 1
// 2
// 4

for (let item of array) {
console.log(`for...of:${item}`);
}
// for...of:1
// for...of:2
// for...of:undefined
// for...of:4

console.log("num:", num); // num: 3

const words = ["one", "two", "three", "four"];
words.forEach((word) => {
console.log(word);
if (word === "two") {
words.shift();
}
}); // one // two // four
console.log(words); // ['two', 'three', 'four']

const words2 = ["one", "two", "three", "four"];
for (let item of words2) {
console.log(`for...of:${item}`);
if (item === "two") {
words2.shift();
}
}
console.log(words2); // [ 'two', 'three', 'four' ]

8、使用 forEach 不会改变原来的数组

调用 ​​forEach()​​ 时,它不会更改原始数组,即调用它的数组。但是那个对象可能会被传入的回调函数改变:

const array = [1, 2, 3, 4];
array.forEach((ele) => {
ele = ele * 3;
});
console.log(array); // [ 1, 2, 3, 4 ]
const numArr = [33, 4, 55];
numArr.forEach((ele, index, arr) => {
if (ele === 33) {
arr[index] = 999;
}
});
console.log(numArr); // [ 999, 4, 55 ]
// 2
const changeItemArr = [
{
name: "wxw",
age: 22,
},
{
name: "wxw2",
age: 33,
},
];
changeItemArr.forEach((ele) => {
if (ele.name === "wxw2") {
ele = {
name: "change",
age: 77,
};
}
});
console.log(changeItemArr); // [ { name: 'wxw', age: 22 }, { name: 'wxw2', age: 33 } ]
const allChangeArr = [
{ name: "wxw", age: 22 },
{ name: "wxw2", age: 33 },
];
allChangeArr.forEach((ele, index, arr) => {
if (ele.name === "wxw2") {
arr[index] = {
name: "change",
age: 77,
};
}
});
console.log(allChangeArr); // // [ { name: 'wxw', age: 22 }, { name: 'change', age: 77 } ]

举报

相关推荐

0 条评论