javaScript-event loop
2021-09-20·6min
type
Post
summary
status
Published
category
tags
slug
date
Sep 20, 2021
password
icon
由于JS是单线程的,为了不因为耗时长的任务而阻塞线程,就需要一种机制来协调和调度各任务。
这个机制,就是所谓的event loop(事件循环)。
同步与异步
在了解event loop之前,让我们首先了解一下,什么是同步,什么是异步。
同步
同步行为对应着按顺序执行的代码,每条代码都会严格按照它们出现的顺序来执行。
当上一条代码执行完毕时,下一条代码就能立即获取对应值的变化。
例如下面这段代码:
就算不打印也知道,会输出2。
这就是同步行为,顺序执行且可预测。
异步
相对于同步行为,异步行为是更像是一个黑盒。
知道它是存在的且会执行的,但不知道它什么时候产生结果。
所以在异步行为有了结果之后,需要通知JS的主线程。这个通知,往往是以回调函数的形式出现。
例如下面这段代码:
这段代码与同步行为的代码一样都是对a进行运算操作,但不同的是这次主线程并不知道a的值什么时候会发生改变。
因为a的值改变取决于定时器内部的回调函数什么时候被执行,所以当我们想实时获取a变化后的值时,只能够在这个定时器的回调函数里去获取。
异步行为可以理解为不需要等待的任务,当这个任务有了结果时,会以回调的形式进行通知。
event loop
首先要知道的是,event loop是一种机制,用来调度任务的执行。
不同的平台对于event loop的实现并不一致,比如浏览器和node.JS的实现就不一样。
浏览器的event loop基于html5中的规范实现,且不同浏览器实现还不一致。
node.JS的event loop基于libuv来实现。
宏任务队列与微任务队列
在了解event loop之前,先认识一下宏任务队列与微任务队列。
宏任务队列(macrotask queue)
一些异步任务在有了结果之后会将回调放入宏任务队列等待执行,我们称这些任务为宏任务(macrotask、tasks)。
宏任务包括:
- setTimeout
- setInterval
- setImmediate(node独有)
- requestAnimationFrame(浏览器独有)
- I/O
- UI rendering (浏览器独有)
微任务队列(microtask queue)
一些异步任务在有了结果之后会将回调放入微任务队列等待执行,我们称这些任务为微任务(microtask、jobs)。
微任务包括:
- promise
- MutationObserver
- process.nextTick(node独有)
浏览器中的event loop
首先要明确知道的是,JS中只有一条主线程,一次只能处理一件事。
当一个任务在执行时,其他任务都要等待。
先看下图:

按图描述一下在浏览器中JS代码执行的具体流程:
- 全局代码script压入执行栈中被执行。其中的同步语句按顺序马上执行,异步语句也会被读取,但会交给对应的浏览器线程处理,在处理有了结果之后,再将回调函数放入宏/微任务队列中等待出列执行。
- 全体代码script执行完毕,执行栈被清空。
- 检查微任务队列中是否有待执行的任务。如果有,放入执行栈中,按顺序执行完队列中所有的微任务,包括在这个过程中新加入队列的微任务。
- 执行栈被清空,微任务队列被清空,检查宏任务队列中是否有等待执行的宏任务。如果有,将队头的第一个宏任务压入执行栈中执行。
- 重复3、4步....
- 重复3、4步....
接下来根据具体代码来进行分析:
整体流程:
- 全局代码script压入执行栈中开始执行
step1:
分析:由于是同步语句,立即执行输出1
打印结果: 1
Stack: [全局代码script]
Macrotask Queue: []
Microtask Queue: []
step2:
分析:setTimeout是宏任务,所以会交给浏览器指定线程处理,有结果(时间到)后再将回调函数放入宏任务队列中等待执行。
打印结果: 1
Stack: [全局代码script]
Macrotask Queue: [callback1]
Microtask Queue: []
step3:
分析:由于promise构造函数是立即执行的同步代码,所以会立即输出4。then的指定回调会在promise状态改变后加入微任务队列。
打印结果: 1 4
Stack: [全局代码script]
Macrotask Queue: [callback1]
Microtask Queue: [callback2]
step4:
分析:setTimeout属于宏任务,有结果(时间到)后将回调函数放入宏任务队列中等待执行。
打印结果: 1 4
Stack: [全局代码script]
Macrotask Queue: [callback1, callback3]
Microtask Queue: [callback2]
step5:
分析:同步代码立即执行输出7。这句代码结束后,全局script代码执行完毕,执行栈被清空。
打印结果: 1 4 7
Stack: []
Macrotask Queue: [callback1, callback3]
Microtask Queue: [callback2]
step6:
分析:开始检查微任务队列,如果有微任务,则依次执行,直至将微任务队列清空。发现微任务队列中有微任务callback2,取出放入执行栈中执行,输出对应的值5,执行结束后出栈。
打印结果: 1 4 7 5
Stack Queue: []
Macrotask Queue: [callback1, callback3]
Microtask Queue: []
step7:
分析:微任务队列被清空,执行栈也为空。开始检查宏任务队列,如果有任务,取出队头任务放入执行栈中执行。发现宏任务队列中有宏任务callback1,取出放入执行栈中执行。执行过程中发现有同步语句立即执行,输出2。又读取到一个解决状态的promise,将then指定回调加入微任务队列中。
打印结果: 1 4 7 5 2
Stack Queue: []
Macrotask Queue: [callback3]
Microtask Queue: [callback4]
step8:
分析:宏任务执行完毕出栈,检查微任务队列,发现微任务callback4,放入执行栈中开始执行,遇见同步代码,立即输出3。执行完毕微任务出栈,微任务队列清空。
打印结果: 1 4 7 5 2 3
Stack Queue: []
Macrotask Queue: [callback3]
Microtask Queue: []
step9:
分析:发现宏任务队列中有宏任务callback3,取出放入执行栈中执行。执行过程中发现有同步语句立即执行,输出6。至此全部代码执行完毕,执行栈为空,宏任务队列为空,微任务队列为空。
最终打印结果: 1 4 7 5 2 3 6
Stack Queue: []
Macrotask Queue: []
Microtask Queue: []
以上就是浏览器中event loop的整个执行流程,这里概括两个重点:
- 宏任务队列一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务;
- 微任务队列中所有的任务都会被依次取出来执行,直至微任务队列为空;