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, b) {
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, b) {
return new Promise((resolve, reject) => {
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 = 1
sum = 2
sum = 3
它们被依次执行。 -
所以输出的结果与预期是有区别的
如何解决上述问题
使用 for...of
循环去处理 async await
的问题,for...of
本质上就是一个 while
循环。
let sum = 0
const arr = [1, 2, 3]
function sumFn (a, b) {
return new Promise((resolve, reject) => {
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)