Event Loop浅析

740 阅读5分钟

前言

  js是单线程的,所有任务依次排队执行,只有前一个任务结束以后才会执行下一个任务,如果遇到一个耗时很久的任务,下一个任务就不得不一直等待。

  js的任务划分为同步任务和异步任务,异步任务包括微任务和宏任务,微任务的执行时机在宏任务之前。运行js的代码会形成一个主线程和一个任务队列,任务队列包含微任务队列和宏任务队列。主线程在执行的过程中,微任务和宏任务分别进入到对应的队列中。

  首先是同步任务进入主线程依次执行,同时异步任务分别进入微任务队列和宏任务队列。当主线程内的同步任务全部执行完毕以后,微任务队列中的微任务会依次进入主线程执行直到队列清空,再执行宏任务。

  在主线程执行过程中,微任务和宏任务同时进入对应的队列,主线程执行完以后,再依次从微任务队列和宏任务队列中取任务执行,上述过程不断重复,也就是Event Loop(事件循环)。

Macrotask和Microtask

  异步任务之间根据执行优先级的区别,划分为宏任务(Macrotask)和微任务(Microtask),微任务的执行时机在宏任务之前。
1. Macrotask(宏任务)

  Macrotask包括:setTimeout、setInterval、setImmediate(Node独有)、requestAnimationFrame (浏览器独有)、I/O、UI rendering(浏览器独有)

2. Microtask(微任务)

  Microtask包括:Promise、Object.observe、MutationObserver、process.nextTick(Node独有)

Macrotask、Microtask和DOM的渲染顺序

Microtask在DOM渲染前执行,Macrotask在DOM渲染后执行。 alert会阻塞线程,包括页面渲染的GUI线程,通过alert的特点来验证微任务、宏任务和DOM节点渲染的顺序。
index.html文件

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="box">
    </div>
    <script>
        let boxDom = document.getElementById("box")
        boxDom.innerHTML="<H1>Hello</H1>"
        setTimeout(()=>{
            console.log('setTimeout')
            alert('setTimeout')
        })
        Promise.resolve().then(()=>{
            console.log('promise')
            alert('promise')
        })
    </script>
</body>
</html>

在DOM渲染之前,先执行微任务promise。

1.png

在DOM渲染以后,再执行宏任务promise。

2.png

浏览器和Node的Event Loop

1. 浏览器
(1)整体代码script进入主线程开始执行
(2)主线程执行的过程中,将异步任务放入任务队列中,任务队列存放有两种类型的异步任务,宏任务队列,微任务队列。
(3)同步任务任务执行完毕后,从微任务队列中依次取出微任务执行,如果在执行微任务的过程中又产生了新的微任务,则将新的微任务加入队列的末尾,在本周期执行
(4)微任务队列执行完毕以后,从宏任务队列中取出下一个宏任务执行
(5)循环(3)—>(4)直到所有的任务执行完毕

3.png

2. Node
node11前:
(1)执行完一个阶段的所有任务
(2)执行完nextTick队列里面的内容
(3)然后执行完微任务队列的内容

4.png
具体可参看:nodejs.org/zh-cn/docs/…
node11后:
与浏览器保持一致,先执行同步任务,再循环执行微任务队列,再执行一个宏任务,…...循环往复。

示例

分别以setTimeout和promise作为微任务和宏任务的代表进行示例。

  1. 微任务和宏任务分别运行
console.log('script start');

setTimeout(() => {
    console.log('setTimeout1');
}, 0);

Promise.resolve().then(()=>{
    console.log('promise1');
})

setTimeout(() => {
    console.log('setTimeout2');
}, 0);

Promise.resolve().then(()=>{
    console.log('promise2');
})

console.log('script end');

// 输出结果
//script start
//script end
//promise1
//promise2
//setTimeout1
//setTimeout2

执行顺序:
(1)执行同步任务,输出:
script start
script end
同时promise1,promise2进入微任务队列,setTimeout1,setTimeout2进入宏任务队列
(2)循环执行微任务队列,输出:
promise1
promise2
(3)执行一个宏任务,输出:
setTimeout1
(4)执行一个宏任务,输出:
setTimeout2
2. 微任务包含微任务,宏任务包含宏任务

console.log('script start');

setTimeout(() => {
    console.log('setTimeout1');
    setTimeout(()=>{
        console.log('setTimeout1-1');
    })
}, 0);

Promise.resolve().then(()=>{
    console.log('promise1');
    Promise.resolve().then(()=>{
        console.log('promise1-1');
    })
})

setTimeout(() => {
    console.log('setTimeout2');
    setTimeout(()=>{
        console.log('setTimeout2-2');
    })
}, 0);

Promise.resolve().then(()=>{
    console.log('promise2');
    Promise.resolve().then(()=>{
        console.log('promise2-2');
    })
})

console.log('script end');

// 输出结果
// script start
// script end
// promise1
// promise2
// promise1-1
// promise2-2
// setTimeout1
// setTimeout2
// setTimeout1-1
// setTimeout2-1

执行顺序:
(1)执行同步任务,输出:
script start
script end
同时promise1,promise2进入微任务队列,由于promise1-1,promise2-2是执行微任务过程中产生的微任务,也放入微任务队列末尾,setTimeout1,setTimeout2进入宏任务队列
(2)执行微任务队列,输出:
promise1
promise2
promise1-1
promise2-2
(3)执行宏任务队列,输出:
setTimeout1
setTimeout2
同时setTimeout1-1,setTimeout2-1进入宏任务队列
(4)执行宏任务队列,输出:
setTimeout1-1
setTimeout2-1
3. 微任务和宏任务交叉包含

console.log('script start');

setTimeout(() => {
    console.log('setTimeout1');
    setTimeout(()=>{
        console.log('setTimeout2');
    })
    Promise.resolve().then(()=>{
        console.log('promise1');
    })
}, 0);

Promise.resolve().then(()=>{
    console.log('promise2');
    Promise.resolve().then(()=>{
        console.log('promise3');
    })
    setTimeout(()=>{
        console.log('setTimeout3');
    })
})

setTimeout(() => {
    console.log('setTimeout4');
    setTimeout(()=>{
        console.log('setTimeout5');
    })
    Promise.resolve().then(()=>{
        console.log('promise4');
    })
}, 0);

Promise.resolve().then(()=>{
    console.log('promise5');
    Promise.resolve().then(()=>{
        console.log('promise6');
    })
    setTimeout(()=>{
        console.log('setTimeout6');
    })
})

console.log('script end');

// 输出结果
// script start
// script end
// promise2
// promise5
// promise3
// promise6
// setTimeout1
// promise1
// setTimeout4
// promise4
// setTimeout3
// setTimeout6
// setTimeout2
// setTimeout5

执行顺序:
(1)执行同步任务,输出:
script start
script end
promise2,promise5,promise3,promise6进入微任务队列,setTimeout1,setTimeout4进入宏任务队列
(2)执行微任务队列,输出:
promise2
promise5
promise3
promise6
setTimeout3,setTimeout6进入宏任务队列
(3)执行第(1)步的宏任务队列setTimeout1,循环输出宏任务下面的微任务,输出:
setTimeout1
promise1
setTimeout2进入新的宏任务队列
(4)执行第(1)步的宏任务队列setTimeout4,循环输出宏任务下面的微任务,输出:
setTimeout4
promise4
setTimeout5进入新的宏任务队列
(5)执行第(2)步的宏任务队列setTimeout3,输出:
setTimeout3
(6)执行第(2)步的宏任务队列setTimeout6,输出:
setTimeout6
(7)执行第(3)步的宏任务队列setTimeout2,输出:
setTimeout2
(8)执行第(4)步的宏任务队列setTimeout4,输出:
setTimeout5