Array.prototype.forEach()
forEach() 方法对数组的每个元素执行一次给定的函数。
语法
arr.forEach(callback(currentValue [, index [, array]])[, thisArg])
-
callback:为数组中每个元素执行的函数,该函数接收一至三个参数:
-
currentValue:数组中正在处理的当前元素。 -
index: 可选,数组中正在处理的当前元素的索引。 -
array: 可选,forEach() 方法正在操作的数组。
thisArg: 可选,当执行回调函数 callback 时,用作 this 的值。
返回值:undefined。
-
forEach() 为每个数组元素执行一次callback 函数;与map() 或者reduce() 不同的是,它总是返回undefined 值,并且不可链式调用。 -
forEach() 被调用时,不会改变原数组(尽管callback 函数在被调用时可能会改变原数组) - 由于
forEach 的返回值是undefined,所以 forEach 无法进行链式调用
使用示例
不对未初始化的值进行任何操作
如下稀疏数组,3 和 7 之间空缺的数组单元未被 forEach() 调用 callback 函数,或进行任何其他操作
const arraySparse = [1, 3, , 7];
let numCallbackRuns = 0;
arraySparse.forEach(function (element) {
console.log(element);
numCallbackRuns++;
});
console.log("numCallbackRuns: ", numCallbackRuns);
如果数组在迭代时被修改了,则其他元素会被跳过
当到达包含值 two 的项时,整个数组的第一个项被移除了,这导致所有剩下的项上移一个位置。因为元素 four 正位于在数组更前的位置,所以 three 会被跳过。
var words = ['one', 'two', 'three', 'four'];
words.forEach(function (word) {
console.log(word);
if (word === 'two') {
words.shift();
}
});
中断循环
在 forEach 中中断循环可以使用 return,因为 forEach 中的 callback 是一个回调函数,在函数中可以使用 return 来中断循环
const arr = [1, 3, 66, 88, 999]
arr.forEach((item) => {
if (item === 66) {
return
}
console.log(item);
})
由于 break 与 continue 必须在一个循环体内使用,所以不能直接在 forEach 里使用,不能直接用它们来中断循环
forEach 对于 async 的处理问题
先看一段正常的代码
let sum = 0
const arr = [1, 2, 3]
function sumFn (a,) {
return a + b
}
function main (array) {
array.forEach(item => {
sum = sumFn(sum, item)
console.log(sum);
})
console.log(sum)
}
main(arr)
输出结果如下
将上述代码改造为 promise 的形式
let sum = 0
const arr = [1, 2, 3]
function sumFn (a,) {
return new Promise((resolve,) => {
setTimeout(() => {
resolve(a + b)
}, 1000 * b)
})
}
async function main (array) {
array.forEach(async item => {
sum = await sumFn(sum, item)
console.log(sum);
})
console.log(sum)
}
main(arr)
为什么打印的结果和上述的不一样呢,分析如下
当代码执行到 forEach 时:
- 首先遇到
sum = await sumFn(sum, item) 语句(注意,它是从右往左执行的)
因此,它会执行 sumFn(0, 1),那么该函数 return 1,
由于 async 函数始终会返回一个 Promise 对象,即 return Promise.resolve(1)。 - 由于
await 的原因,它其实相当于执行 Promise.resolve(1).then() 方法,
它属于微任务,会暂时 Hold 住,被放入微任务的队列,待本次同步任务执行完之后,
才会被执行,因此并不会立即赋值给 sum(所以 sum 仍为 0)。 - 那
JS 引擎主线程不会闲着的,它会继续执行“同步任务”,即下一次循环。
同理,又将 return Promise.resolve(2) 放入微任务队列。
直到最后一次循环,同样的的还是 return Promise.resolve(3)。
其实到这里,forEach 其实算是执行完了。
以上示例,forEach 的真正意义是创建了 3 个微任务。 - 由于主线程会一直执行同步任务,待同步任务执行完之后,才会执行任务队列里面的微任务。
待 forEach 循环结束之后,自然会执行 console.log(sum),
但注意,由于 await 的原因,sum 一直未被重新赋值,因此 sum 还是为 0 ,
所以控制台输出了 0。 - 等
console.log(sum) 执行完毕,才开始执行队列中的微任务,
其中 await Promise.resolve(0) 的结果,
相当于 Promise.resolve(0) 的 then 方法的返回值,
所以此前的三个微任务,相当于:
sum = 1sum = 2sum = 3 它们被依次执行。 - 所以输出的结果与预期是有区别的
如何解决上述问题
使用 for...of 循环去处理 async await 的问题,for...of 本质上就是一个 while 循环。
let sum = 0
const arr = [1, 2, 3]
function sumFn (a,) {
return new Promise((resolve,) => {
setTimeout(() => {
resolve(a + b)
}, 1000 * b)
})
}
async function main (array) {
for (const item of array) {
sum = await sumFn(sum, item)
console.log(sum);
}
// 相当于
// const iterator = array[Symbol.iterator]()
// let iteratorResult = iterator.next()
// while (!iteratorResult.done) {
// sum = await sumFn(sum, iteratorResult.value)
// iteratorResult = iterator.next()
// }
console.log(sum)
}
main(arr)










