概念
JavaScript是一门单线程语言,即同一时间只能执行一个任务,代码执行时是阻塞的。 因此浏览器提供了一些JS引擎不具备的功能: Web API
,包含DOM API
,setTimeout
,HTTP请求
等
在了解事件循环之前先了解俩个简单的概念
队列
队列是一种FIFO(First in, First out)数据结构,特点:先进先出
栈
栈是一种LIFO(Last in, First out)数据结构,特点:后进先出
调用栈
调用栈 是js引擎中的一部分,本质上就是栈, 当函数被调用时,会进入到调用栈中。
- 函数A执行,函数A入栈
- 函数A中调用函数B,函数B入栈
- 函数B执行完 出栈
- 然后继续执行函数A,执行完函数A出栈
- 栈空
function B() {
console.log('B')
}
function A() {
B()
console.log('A')
}
A()
// 执行函数A -> 入栈,调用函数B -> 入栈,函数B执行完毕 -> 出栈,函数A执行完毕 -> 出栈。
2
3
4
5
6
7
8
9
上述都是同步代码,调用栈就能解释,但往往js中不仅仅是同步代码,还包含
setTimeout
、网络请求
等异步任务这时就需要引入
Event Table(事件表格)
和Event Queue(事件队列)
。
事件表格 & 事件队列
Event Table 可以理解成一张 事件->回调函数 对应表
它就是用来存储 JavaScript 中的异步事件 (request, setTimeout, IO等) 及其对应的回调函数的列表
Event Queue 简单理解就是 回调函数 队列,所以它也叫 Callback Queue
当 Event Table 中的事件被触发,事件对应的 回调函数 就会被 push 进这个 Event Queue,然后等待被执行
任务
在JavaScript中,任务被分为俩种:同步任务
& 异步任务
异步任务又包含 宏任务
,微任务
宏任务:
script
整体代码、setInterval
、setTimeout
、setImmediate
(浏览器暂不支持)、I/O
、UI Rendering
。
微任务:
Process.nextTick
(node专有)、Promise
、Object.observe
(废弃)、MutationObserver
(具体使用方式查看这里open in new window)
Event Loop
先上流程图:
- 当代码开始进入
script(宏任务)
,会从上往下逐行执行 - 同步任务直接在栈内等待被执行,异步任务会从
Call Stack
移入到Event Table
注册 - 当
Event Table
中相对应的事件触发(或定时器时间到),Event Table
会根据异步任务类型 分别将回调函数移动到宏Event Queue
|微Event Queue
排队等待 - 当
Call Stack
为空时,会从微Event Queue
依次取出微任务到Call Stack
执行,直至微Event Queue
被清空。
- 注意 在执行微任务过程中,产生新的微任务,会加入到队列末尾,会在此周期内被调用执行。
- 注意 当一个宏任务周期内所有微任务执行完毕之后,再执行下个宏任务之前,浏览器会自行判断执行UI Rendering。
- 从
宏Event Queue
取出宏任务放入Call Stack
执行,重复 2-4步骤。
看下下面这道题哦, 执行环境
chrome控制台
console.log(1)
setTimeout(() => {
console.log(6)
setTimeout(() => {
console.log(7)
})
console.log(8)
})
setTimeout(() => {
console.log(2)
Promise.resolve(3).then(console.log)
Promise.reject(4).catch(console.log)
console.log(5)
})
new Promise((resolve, reject) => {
console.log(9)
resolve(10)
console.log(11)
}).then(res => {
console.log(res)
Promise.resolve(12).then(console.log)
})
new Promise((resolve, reject) => {
console.log(13)
setTimeout(() => {
console.log(14)
})
console.log(15)
reject(16)
}).catch(console.log)
console.log(17)
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
36
答案
1 9 11 13 15 17 10 16 12 6 8 2 5 3 4 14 7
先分析
- 代码开始执行,整体代码属于script(宏任务),从上往下执行
- 同步任务会在
Call Stack
等待被执行,异步任务会放入到Event Table
注册
此时 console.log(1) 、Promise函数体、 console.log(17)是同步任务 等待被执行
其它任务都作为异步任务移入到
Event Table
中Promise函数体中遇到的异步任务也是在同一周期内放入到
Event Table
中因此先输出 1 9 11 13 15 17
- 此时
Call Stack
执行栈同步任务执行完毕,先检查微任务队列,发现以下微任务resolve(10)
,reject(16)
,依次执行,resolve(10)
微任务中又遇到微任务resolve(12)
放入到微任务队列最后,(要执行下个宏任务,必须清空微任务队列)
注意::此时Event Table中异步任务都基本被触发或时间到(如果有定时时间长的,可能此时还在
EventTable
中因此输出 10 16 12
- 微任务队列清空,
Call Stack
从宏任务队列取出队首的宏任务,重复Event Loop循环
setTimeout(() => {
console.log(6)
setTimeout(() => {
console.log(7)
})
console.log(8)
})
2
3
4
5
6
7
执行同步任务 ,微任务放入微任务队列 宏任务 放入宏任务队列排队等待
先输出 6 8 然后检查微任务队列 为空 ,执行下个宏任务 输出 6 8
- 执行下个宏任务
setTimeout(() => {
console.log(2)
Promise.resolve(3).then(console.log)
Promise.reject(4).catch(console.log)
console.log(5)
})
2
3
4
5
6
执行同步任务 ,微任务放入微任务队列 宏任务 放入宏任务队列排队等待
先输出 2 5 然后检查微任务队列 ,清空微任务队列 输出 3 4 然后执行下个宏任务
- 执行下个宏任务 一直循环