JavaScript事件循环(Event Loop)
任务队列
首先我们要知道关于JavaScript的一些规则:
- JavaScript是被设计成单线程的
- JavaScript的任务分为同步任务和异步任务
同步任务都在主线程上执行,形成一个执行栈。当主线程执行完之后,运行微任务(micro-task)队列的任务直到为空,更新UI渲染(会根据浏览器的逻辑,决定要不要马上执行更新),然后再运行宏任务(macro-task)队列的任务直到为空......流程如下:
(主线程上的执行栈同步任务,可以视为是第一个macro-task队列) macro-task -> micro-task(如果存在) -> 更新UI渲染
如此无限循环上面的流程,是为JavaScript的Event Loop机制。
宏任务
宏任务(macro-task),宏任务队列可以有一个或者多个。每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。
宏任务:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering.
微任务
微任务(micro-task),微任务在渲染更新前,macro-task之后执行。
关于async和await,因为async await 本身就是promise+generator的语法糖。所以await后面的代码是microtask。实际上await是一个让出线程的标志。await后面的表达式会先执行一遍,将await后面的代码加入到microtask中,然后就会跳出整个async函数来执行后面的代码。
微任务:process.nextTick, Promise, Object.observer, MutationObserver,await.
举例
async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { console.log('async2'); } console.log('script start'); setTimeout(function () { console.log('setTimeout'); }, 0) async1(); new Promise(function (resolve) { console.log('promise1'); resolve(); }).then(function () { console.log('promise2'); }); console.log('script end');
流程如下:
- console打印script start
- setTimeout,是异步宏任务,进入macro-task setTimeout队列
- async1(), async await函数,在await之前是同步任务,直接执行,打印async1 start
- await async2(),await后面的表达式会先执行一遍,打印async2
- await 下面的代码视为promise.then,进入micro-task promise队列,跳出async1()
- new Promise,promise内,.then之前的代码是直接执行的,所以打印promise1
- .then内函数进入micro-task promise队列后
- console,直接打印script end
- 主线程执行栈运行完并清空了,micro-task进入执行栈,分别按顺序执行打印async1 end 和 promise2。
- micro-task队列清空,macro-task进入执行栈,打印setTimeout,程序运行完毕。
完整结果如下:
/** *script start *async1 start *async2 *promise1 *script end *async1 end *promise2 *setTimeout */
该结果基于chrome 版本 72.0.3626.121。因为async await标准有所改变,所以稍老版本的浏览器结果可能不一致。
参考:
https://jakearchibald.com/201...
https://github.com/Advanced-F...