浏览器事件循环(Event Loop)
彻底搞懂!
有多少种任务:同步任务,微任务,宏任务
在 JavaScript 引擎(V8)的字典里,根本没有“异步任务”这个词,只有“同步任务”和“回调函数”。
同步任务属于宏任务,但宏任务不一定是同步任务。所有的同步代码,其实都是第一个默认的“宏任务”。
真正的微任务只有这几个:Promise.then/catch/finally、MutationObserver、queueMicrotask。
常见的宏任务:setTimeout、setInterval、I/O操作、UI 渲染、全局 script 代码。普通的异步请求(如 fetch / ajax)虽然它们是异步的,但它们属于 I/O 操作,执行完后的回调会被放入宏任务队列中等待执行。
有多少种任务队列:微任务队列,宏任务队列
类比:厨师为js引擎(单线程)即一整个js代码片段,厨师要做什么菜是同步任务,客人又点了可乐但是厨师在炒饭,所以厨师直接给了服务员(webapis)去做可乐(“异步任务”-区分同步任务的说法,这里的可乐我们给他设定为promise.then),在做完菜之后,微任务有插队机制,所以厨师需要先给可乐,结束所有微任务之后再给炒饭,之后厨师就回去做下一道菜,从宏任务里面取出同步任务继续循环。
【执行一个宏任务】 ➡️ 【清空所有微任务】 ➡️ 【执行下一个宏任务】 ➡️ 【清空所有微任务】...(无限循环)
console.log('1. 同步代码'); setTimeout(() => { console.log('2. setTimeout 宏任务'); // 注意:在宏任务内部又产生了微任务 Promise.resolve().then(() => { console.log('7. 第一个 setTimeout 里的微任务'); }); }, 0); setTimeout(() => { console.log('8. 第二个 setTimeout 宏任务'); }, 0); Promise.resolve().then(() => { console.log('3. Promise 微任务'); }); async function asyncFn() { console.log('4. async 函数内的同步代码'); await Promise.resolve(); console.log('5. async 函数内 await 后的代码'); } asyncFn(); console.log('6. 同步代码结束');✅ 正确的打印顺序是:1 -> 4 -> 6 -> 3 -> 5 -> 2 -> 7 -> 8
阅读整个js片段,js引擎开始执行,同步代码直接输出,将微任务放入微任务队列,宏任务放入宏任务队列
第一轮:
console输出1
第一个settimeout的回调进入宏任务,不管
第二个settimeout的回调进入宏任务,不管
promise的回调,进入微任务,不管
调用函数asyncFn() -> 进入内部 -> console输出4 -> await 关键字后面的代码,相当于被放进了 Promise.then 中,所以直接进入微任务,不管
console输出6
结果:1 4 6
第二轮:
检查微任务队列
promise的回调,console输出3
await 关键字后面的代码,console输出5
结果:1 4 6 3 5
第三轮:
检查宏任务队列
第一个settimeout,console输出2,后面跟了一个promise的回调,进入微任务,不管
这个时候当前宏任务结束,在下一个宏任务开始之前,检查微任务队列
结果:1 4 6 3 5 2
第四轮:
检查微任务队列
promise的回调,console输出7,微任务结束
结果: 1 4 6 3 5 2 7
第五轮:
检查宏任务队列
第二个settimeout,console输出8
结果:1 4 6 3 5 2 7 8
