JS 本质是单线程的,通过回调函数来实现异步操作。也就是说,在 JS 中,立即执行的代码叫做同步任务;过了一段时间之后,满足了一定条件时才执行的代码叫做异步任务。
按照这种分类,JS 的执行机制是这样的:
- 首先判断 JS 是同步还是异步,同步就进入主进程,异步就进入
event table
- 异步任务在
event table
中注册函数,当满足触发条件后,被推入event queue
- 当主进程中的任务执行完时,将
event queue
的任务推入主进程
好的,我们现在来看一个例子:
setTimeout(() => console.log(1))
new Promise(function (resolve) {
console.log(2)
resolve()
}).then(() => {
console.log(3)
})
console.log(4)
如果我们按照上面的执行机制来看这串代码,应该是这样的:
setTimeout
是异步任务,进入event table
,注册回调函数并将其推入event queue
new
是同步任务,执行new Promise()
,打印 ‘2’Promise().then()
是异步任务,进入event table
,注册回调函数并将其推入event queue
- 执行
console.log()
,打印 ‘4’ - 主线程执行完毕,将
setTimeout
的回调函数推入主线程并执行,打印 ‘1’ - 将
Promise().then()
的回调推入主线程,打印 ‘3’
这样看下来打印的结果应该是:‘2’ , ‘4’ , ‘1’ , ‘3’
但是,实际上的结果却是 ‘2’ , ‘4’ , ‘3’ , ‘1’ ,原因是 setTimeout
和 Promise().then()
不是一个 event queue
。原来,JS 单纯的按照同步任务和异步任务区分是不准确的,准确的分类是 宏任务 和 微任务:
- 宏任务:同步代码、
setTimeout
、setInterval
- 微任务:
Promise
按照这种分类,JS 的执行机制是:
- 首先在主线程中执行宏任务,当遇到宏任务的异步代码,会将其回调函数放到宏任务队列中,当遇到微任务时,会将其回调函数放到微任务队列中。
- 当主线程中的任务执行完毕后,先将微任务队列中的任务推入主线程中执行
- 直到当微任务队列中没有任务时,才将宏任务队列中的任务推入主线程中进行下一循环
以上,就是 Event Loop
现在,在让我们来开上边那串代码,应该是这样的:
setTimeout
是异步的宏任务,将其回调函数推入宏任务队列new
是同步任务,在主线程执行,打印 ‘2’Promise().then()
是微任务,将其回调函数推入微任务队列- 执行
console.log()
,打印 ‘4’ - 主线程执行完毕,将微任务队列中的任务推入主线程并执行,即
Promise().then()
打印 ‘3’ - 微任务队列空了,进入下一循环
- 将宏任务队列中的任务推入主线程并执行,打印 ‘1’
这样,我们得到了正确结果: ‘2’ , ‘4’ , ‘3’ , ‘1’
了解了本质之后,就可以来看些进阶的例子了:
setTimeout(() => {
console.log(1)
new Promise(resolve => {
console.log(2)
resolve()
}).then(() => {
console.log(3)
})
})
new Promise(resolve => {
console.log(4)
resolve()
}).then(() => {
setTimeout(() => console.log(5))
new Promise(resolve => {
console.log(6)
resolve()
}).then(() => {
console.log(7)
})
console.log(8)
})
console.log(9)
还是按照上面的执行机制,来看下结果:
第一次循环:
setTimeout
是异步的宏任务,将其回调函数推入宏任务队列new
是同步任务,打印 ‘4’Promise().then()
是微任务,将其回调函数推入微任务队列- 执行
console.log()
,打印 ‘9’ - 将微任务队列中的任务推入主线程并执行
setTimeout
是异步的宏任务,将其回调函数推入宏任务队列new
是同步任务,打印 ‘6’Promise().then()
是微任务,将其回调函数推入微任务队列- 执行
console.log()
,打印 ‘8’ - 微任务队列不为空,将微任务队列中的下一个任务推入主线程并执行,打印 ‘7’,微任务队列为空
第二次循环:
- 将宏任务队列中的任务推入主线程,打印 ‘1’
new
是同步任务,打印 ‘2’Promise().then()
是微任务,将其回调函数推入微任务队列- 微任务队列不为空,将微任务队列中的任务推入主线程并执行,打印 ‘3’,微任务队列为空
第三次循环:
- 将宏任务队列中的下一任务推入主线程,打印 ‘5’
最终结果为:‘4’, ‘9’, ‘6’, ‘8’, ‘7’, ‘1’, ‘2’, ‘3’, ‘5’