深入理解forEach与forof
本篇博文由于时间较紧,以结论性叙述为主。
# 一、前言
在之前的随笔中已经对forEach/for..in/for..of
有过相关的学习了解,那篇博文核心解决的是:How to use
。核心结论是:
建议使用
for..in
去遍历Object
,不建议去遍历Array
。PS:在手写深拷贝中,为了形式的美感与统一,还是会用
for...in
去遍历数组,只需要Object.keys()
以及hasOwnProperty
去除掉原形属性即可。建议使用
for...of
遍历所有可迭代对象。
以上,主要是从各API的应用场景的角度去分析的,实际上还有一个非常值得关注点是 forEach
与 for...of
在执行异步任务
时的区别。
# 二、一个例子
下面这里例子中,我们希望依次做出以下行为:隔5秒打印出5,隔4秒打印出4,隔3秒打印出3,隔2秒打印出2,隔1秒打印出1。
let array = [5,4,3,2,1]
function delayLog(item) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(item)
}, 1000 * item)
})
}
async function processArr(arr) {
// 1. 使用 forEach 遍历
arr.forEach(async item=> {
console.log( await delayLog(item)) // 1,2,3,4,5
});
// 2. 使用 for...of 遍历
for (item of arr) {
console.log(await delayLog(item)) // 我们希望得到的结果
}
console.log('打印完成')
}
processArr(array)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 三、Why?
forEach
打印的结果满足不了我们对异步执行顺序执行的预期效果,首先了解下forEach
的底层是如何实现的:
const myForEach = function(fn) {
let i
for(i=0; i<this.length; i++){
fn(this[i], i)
}
}
2
3
4
5
6
从上可知,forEach
本质上还是一个同步执行函数,如果在forEach
中存在异步执行函数,直接就把异步任务直接丢给事件队列线程处理了,因此forEach
不会阻塞代码的执行。
而for...of
实现机制是使用的iterator
迭代器对可迭代对象进行遍历。
可迭代数据类型:原生具有
[Symbol.iterator]
属性数据类型为可迭代数据类型。如数组、类数组(如arguments
、NodeList
)、Set
和Map
。
let arr = [3, 2, 1];
// 这就是迭代器
let iterator = arr[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// {value: 3, done: false}
// {value: 2, done: false}
// {value: 1, done: false}
// {value: undefined, done: true}
2
3
4
5
6
7
8
9
10
11
12
使用迭代器重新实现for...of
的操作
let array = [5,4,3,2,1]
function delayLog(item) {
...同上
}
async function processArr(arr) {
// 1. 使用 for...of 去遍历异步代码
// for (item of arr) {
// console.log(await delayLog(item))
// }
// 2. 使用 forEach 去遍历异步代码
// arr.forEach(async item=> {
// console.log( await delayLog(item))
// });
// 3. 使用 迭代器 去遍历异步代码
let iterator = array[Symbol.iterator]()
let res = iterator.next()
console.log('res', res);
while (!res.done) {
await delayLog(res.value)
console.log(res.value);
res = iterator.next()
}
console.log('打印完成')
}
processArr(array)
// 打印结果
// 5
// 4
// 3
// 2
// 1
// 打印完成
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 四、总结
for...of
就是迭代器iterator
的语法糖。forEach
就是for
语句的语法糖。对于异步代码的执行,之前的做法是使用
map
,将所有异步请求疯转成多个promise
的数组,然后再通过promise.all
拿到所有的res
结果。但自从手写过
promise.all
这个语法后对这种方法有了深刻的理解,为了满足异步的顺序执行,需要借助promise
的能力。实际上promise.all
也就是依次resolve
一下数组内的Promise
对象,并且缺乏对中间结果灵活处理(当然也可以使用allSettled
,或者.then
回调解决)。但是无论哪儿种方式,对于简单的异步函数顺序回调,这种处理手法就略显得有些重。这种操作就像是在React中编写页面,如果只是做简单的数据渲染, 没必要使用
类组件
的形式,还得去继承React内置的Component
提供的一大堆可能完全用不到的生命周期钩子。