深入理解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提供的一大堆可能完全用不到的生命周期钩子。