谈谈对于JS事件循环执行机制以及关于微任务microtask/宏任务macrotask的个人理解

众所周知,JS 是单线程语言,事件执行只能依次进行。但是 JS 的事件有时会发生延迟执行,例如 setTimeout、Promise 之类的方法。那么,我们该如何判断事件执行的先后顺序呢?

1. 同步事件和异步事件

对于 JS 事件,我们可以分为 同步(Synchronous)和 异步(Asynchronous)两种事件类型,执行顺序:先同步再异步

同步事件例如:console.log(1)、new Promise((resolve, reject) => { // ... })  等

异步事件例如:setTimeout、setInterval、Promise.resolve().then(() => { // ... }) 等

JS 事件循环(Event Loop),顾名思义,存在一个事件循环机制,在每一次 JS 事件循环的时候,总是先执行同步,再执行异步,这句话可能有点模糊,先来看代码:

// 例1:
console.log(1)  // 同步事件

setTimeout(function() { // 异步事件
    console.log(2)
}, 0) /* 即使延迟为 0 它也是异步事件 */

new Promise((resolve, reject) => { // 同步事件
    console.log(3)
})

console.log(4) // 同步事件

上述代码执行结果为: 1 -> 3 -> 4 -> 2
解释:如之前所述,先同步再异步。(其实这里有两层事件循环,具体请往下看)

2.事件队列(Event Queues)

什么是事件队列?你可以理解为,在 JS 执行事件循环的时候,会将所有的异步事件放入事件队列中去,所以它就相当于是一个包裹着待执行的异步事件的容器。

不理解?我们来对上面那个例1进行分析:

① console.log(1),判断为同步,立即执行,输出 1;

② setTimeout,判断为异步,放入事件队列(event queues)中去;

③ new Promise,判断为同步,立即执行,输出 3;

④ console.log(4),判断为同步,立即执行,输出 4。

此为第一轮事件结束,在确认没有同步事件后,接下来进入第二轮事件,此时的事件队列中有个异步事件,分析得知,该事件中有一个同步事件 console.log(2),ok,立即执行,事件循环结束。为了加深理解,来个例子:

// 例2
console.log(1)

setTimeout(() => {
    console.log(2)
}, 0)

new Promise((resolve, reject) => {
    console.log(3)
    setTimeout(() => {
        console.log(4)
        resolve()
    }, 0)
}).then(() => {
    console.log(5)
})

console.log(6)

上述代码,你可能还是有些困惑,我们先来分析一波(注意:本着这样一个原则,遇到同步,立即执行;遇到异步,放入队列,等待执行):

先第一轮事件,Event Loop 1:

① console.log(1),同步,立即执行,结果 1;

② setTimeout,异步,放入队列,记为队列1,等待执行;

③ new Promise() 中的 console.log(3),同步,立即执行,结果 3,这里还有个 setTimeout,所以也列为异步,放入队列,记为队列2,等待执行;

④ new Promise.then(),异步,放入队列,记为队列3,等待执行;

⑤ console.log(6),同步,立即执行,结果 6

此时,第一轮时间分析结束

第二轮事件分析,此时的事件队列中有三个,Event Loop 2:

① 队列1中有一个 console.log(2),同步,立即执行,结果 2;

② 队列2中有一个 console.log(4),同步,立即执行,结果 4;

③ 队列3中有一个 console.log(5),同步,立即执行,结果 5.

至此,事件循环结束(没有事件队列存在)。所以结果为:1 -> 3 -> 6 -> 2 -> 4 -> 5

3.宏任务(macrotask)和微任务(microtask)

说完了上面的任务队列,再说说事件机制中的宏任务和微任务。

宏任务和微任务都属于异步任务,换句话说,异步队列中的任务有两种类型,即宏任务和微任务

哪些是宏任务,哪些是微任务呢?

setTimeout、setInterval、setImmediate等这些常见的属于宏任务;

Promise.then()、process.nextTick(类似node.js版的"setTimeout")等这些常见的属于微任务

注意:在一个队列中,总是先执行所有微任务,再执行宏任务。上代码:

// 例3
console.log(1) // --------------------Event Loop 1: 同步1-1

setTimeout(() => { // ----------------Event Loop 1: 异步,队列1-1,(宏)-----Event Loop 2: 队列2-1
    console.log(2)
}, 0)

process.nextTick(() => { // ----------Event Loop 1: 异步,队列1-2,(微)
    console.log(3)
})

new Promise((resolve, reject) => { //-Event Loop 1: 同步1-2
    console.log(4)
    resolve()
}).then(() => { // -------------------Event Loop 1: 异步,队列1-3,(微)
    console.log(5)
})

console.log(6) // --------------------Event Loop 1: 同步1-3

老规矩,先来分析一波再说(同步不再详说,相信你已经完全理解了,主要看异步中的宏任务和微任务):

第一轮事件分析,Event Loop 1:

① 先同步,分别是同步1-1/1-2/1-3,结果 1 -> 4 -> 6 ;

② 再看异步,此时队列中有三个队列,队列1-1/1-2/1-3,先知行微任务,在执行宏任务;

③ 队列中的微任务为:队列1-2和1-3,所以结果 3 -> 5;

④ 队列中的宏任务:队列1-1,丢入下一个队列2-x中

第二轮事件分析,Event Loop 2:

① 队列2-x中,只有一个队列2-1,结果 2;

至此,事件循环结束,例3的结果为:1 -> 4 -> 6 -> 3 -> 5 -> 2

通过上述三个例子,你是否已经或者大致了解了这些基本概念呢?再来一个例子,加深巩固一下:

// 例4
console.log(1)  // ---------------------Event Loop 1: 同步1-1

setTimeout(()=> {  // ------------------Event Loop 1: 异步,队列1-1,(宏)-----Event Loop 2: 队列2-1
    console.log(2)  // -------------------------------------------------------Event Loop 2: 同步2-1-1
    
    process.nextTick(() => {  // ---------------------------------------------Event Loop 2: 异步,队列2-1-1,(微)
        console.log(3)
    })
        
    new Promise((resolve, reject) => {  // -----------------------------------Event Loop 2: 同步2-1-2
        console.log(4)
        resolve()
    }).then(() => {  // ------------------------------------------------------Event Loop 2: 异步,队列2-1-2,(微)
        console.log(5)
    })
}, 0)

process.nextTick(() => {  // ----------Event Loop 1: 异步,队列1-2,(微)
    console.log(6)
})

new Promise((resolve, reject) => { //--Event Loop 1: 同步1-2

    console.log(7)
    resolve()
}).then(() => {  // -------------------Event Loop 1: 异步,队列1-3,(微)
    console.log(8)
})

setTimeout(() => {  // ----------------Event Loop 1: 异步,队列1-4,(宏)-----Event Loop 2: 队列2-2
    console.log(9)  // ------------------------------------------------------Event Loop 2: 同步2-2-1

    process.nextTick(() => {  // --------------------------------------------Event Loop 2: 异步,队列2-2-1,(微)
        console.log(10)
    })
        
    new Promise((resolve, reject) => {  // ----------------------------------Event Loop 2: 同步2-2-2
        console.log(11)
        resolve()
    }).then(() => {  // -----------------------------------------------------Event Loop 2: 异步,队列2-2-2,(微)
        console.log(12)
    })
})

console.log(13)    // ------------------Event Loop 1: 同步1-3

看起来很复杂,为了加深理解,继续分析:

第一轮事件分析,Event Loop 1:

① 先同步,分别是同步1-1/1-2/1-3,结果 1 -> 7 -> 13 ;

② 再看异步,此时队列中有四个队列,队列1-1/1-2/1-3/1-4,先知行微任务,在执行宏任务;

③ 队列中的微任务为:队列1-2和1-3,所以结果 6 -> 8;

④ 队列中的宏任务:队列1-1和1-4,丢入下一个队列2-x中

第二轮事件分析,Event Loop 2:

① 队列2-1中:先同步,分别是同步2-1-1/2-1-2,结果 2 -> 4;

② 队列2-1中:再看异步,此时有两个队列,队列2-1-1/2-1-2,且都是微任务,那就按顺序执行,结果 3 -> 5。此时队列2-1执行结束。

③ 队列2-2中:先同步,分别是同步2-2-1/2-2-2,结果 9 -> 11;

④ 队列2-2中:再看异步,此时有两个队列,队列2-2-1/2-2-2,且都是微任务,那就按顺序执行,结果 10 -> 12。此时队列2-2执行结束。

至此,事件循环结束,结果是:1 -> 7 -> 13 -> 6 -> 8 -> 2 -> 4 -> 3 -> 5 -> 9 -> 11 -> 10 -> 12。

 

emmmmm......大致就是这么回事。

如果理解有误,还请大神指点,THKS:)

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值