执行栈和Event Loop
行栈是一个存储函数调用的栈结构,遵循先进后出的原则。当开始执行 JS 代码时,首先会执行一个 main
函数,然后执行我们的代码。根据先进后出的原则,后执行的函数会先弹出栈。
当遇到异步的代码时,会被挂起并在需要执行的时候加入到 Task(有多种 Task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说 JS 中的异步还是同步行为。
不同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务(microtask) 和 宏任务(macrotask)。在 ES6 规范中,microtask 称为 jobs
,macrotask 称为 task
。
Event Loop
处在两者之间,扮演一个大管家的角色,它会以一个固定的时间间隔不断轮询,当它发现主线程空闲,就会去到 Task
队列里拿一个异步回调,把它塞入执行栈中执行,一段时间后,主线程执行完成,弹出上下文环境,再次空闲,Event Loop
又会执行同样的操作。。。依次循环,于是构成了一套完整的事件循环运行机制。
Event Loop执行顺序
console.log('script start')
async function async1() {
await async2() //执行完后返回了一个Promise.resolve(undefined),该回调被推入 microtask ,async1 函数中的执行权被让出,等待主线程空闲
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() { //引擎解析至 setTimeout,等待 0ms 后将其回调推入 macrotask,执行权继续让出
console.log('setTimeout')
}, 0)
new Promise(resolve => { //解析进入注入函数的内部,碰到 console.log,于是打印 Promise,再往下,碰到了 resolve,此时,该回调被推入 microtask ,执行权被让出
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end') //至此,主线程空闲,Event Loop 事件循环启动,开始从 microtask 里拿出 promise 回调,放入主线程执行
// log 打印顺序:script start -> async2 end -> Promise -> script end -> async1 end-> promise1 -> promise2 -> setTimeout
首先拿出最早注入的 async2
的 Promise.resolve(undefined)
执行,此时 await 操作符解析该表达式,得到结果 undefined,并将 async1 [Promise] 函数 标志为 resolve 状态,将 await 后面的代码作为回调,继续推入 microtask,等待执行,执行权被让出
此时主线程没有可执行的代码,再次空闲,Event Loop 启动,去 microtask 中拿到之前的 new Promise
回调,放入主线程执行,打印结果 promise1
和 promise2;
主线程空闲,Event Loop
去 microtask
里拿 aysnc1
的回调,打印出 async1 end
最后,主线程空闲,microtask
队列空,Event Loop
去 macrotask
里拿到 setTimeout
的回调,放入主线程,打印最后的 setTimeout
(最新版的 chrome 浏览器对于 await 的处理变快了,async1 end 会先于 promise 1 打印)。
Event Loop 执行顺序如下所示:
- 首先执行同步代码,这属于宏任务
- 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
- 执行所有微任务
- 当执行完所有微任务后,如有必要会渲染页面
- 然后开始下一轮 Event Loop,执行宏任务中的异步代码,也就是
setTimeout
中的回调函数
微任务包括 process.nextTick
,promise
,MutationObserver
,其中 process.nextTick
为 Node 独有。
宏任务包括 script
, setTimeout
,setInterval
,setImmediate
,I/O
,UI rendering
。