最近在复习 JavaScript 的过程中,我遇到稍微复杂一点的执行顺序题,就开始靠“感觉”判断。我尝试用一段代码,把 JavaScript 的执行顺序一次性讲清楚。
一段代码
asyncfunctionasync1(){console.log('async1')awaitasync2()console.log('async1 end')}asyncfunctionasync2(){console.log('async2')}console.log('script start')setTimeout(()=>{console.log('setTimeout')},0)async1()newPromise((resolve)=>{console.log('promise')resolve()}).then(()=>{console.log('promise then')})console.log('script end')//执行顺序script start async1 async2 promise script end async1 end promise then setTimeoutJavaScript 执行顺序的核心规则
在分析之前,先记住这 4 条规则:
1️⃣ JavaScript 是单线程的
同一时间只做一件事。
2️⃣ 同步代码优先执行
所有同步代码直接进入调用栈(Call Stack)。
3️⃣ 微任务优先于宏任务
每一轮事件循环中:
先清空所有微任务,再执行一个宏任务
4️⃣ async / await 的本质是 Promise
awaitfn()等价于:
Promise.resolve(fn()).then(()=>{// await 后的代码})逐步拆解执行过程
① 执行同步代码(主线程)
console.log('script start')输出:
script start② 注册 setTimeout(宏任务)
setTimeout(()=>{console.log('setTimeout')},0)这里只是注册,不会立刻执行。
setTimeout 是宏任务(Macro Task),JavaScript 的执行环境中有 调用栈 + 任务队列,setTimeout注册的回调会进入 宏任务队列,等待当前同步代码和所有微任务执行完,0 毫秒只是最短延迟,不是立即执行。浏览器有最小时间限制(HTML5标准规定至少 4ms),Node.js 也会将其加入事件循环的下一个 tick所以 setTimeout(fn, 0) 表示:“尽快在当前事件循环结束后执行 fn”,但绝不是同步立即执行。
③ 执行 async1()
console.log('async1')输出:
async1④ 遇到 await async2()
console.log('async2')输出:
async2注意:
async2()本身是同步执行的await后面的代码被放入微任务队列
⑤ Promise 构造函数是同步的
console.log('promise')输出:
promise⚠️ 很多人误以为 Promise “一创建就是异步”,这是常见误区。
⑥ 继续执行同步代码
console.log('script end')输出:
script end开始清空微任务队列
此时微任务队列中有两个任务:
await后的async1 endpromise.then
按进入顺序执行:
console.log('async1 end')console.log('promise then')输出:
async1 end promise then最后执行宏任务
console.log('setTimeout')输出:
setTimeout一张执行顺序模型表
同步代码 ↓ 微任务(全部清空) ↓ 宏任务(执行一个) ↓ 微任务(再清空) ↓ 下一轮事件循环