娄底市网站建设_网站建设公司_UX设计_seo优化
2025/12/21 22:05:17 网站建设 项目流程

宏任务 / 微任务执行顺序(经典面试题详解)

你关注的宏任务、微任务执行顺序是前端面试的核心考点,尤其围绕setTimeoutPromise.thenasync/await的执行逻辑,我会从概念定义、执行机制到经典例题,帮你彻底理清。

一、先明确:宏任务(Macrotask)vs 微任务(Microtask)

1. 宏任务(Macrotask)

宏任务是浏览器 / Node.js 执行的大型异步任务单元,每次执行一个宏任务,执行完成后会先清空所有微任务,再进入下一个宏任务。

常见宏任务类型:

  • setTimeoutsetInterval(定时器异步任务)
  • setImmediate(Node.js 环境)
  • requestAnimationFrame(浏览器环境)
  • 脚本整体代码(<script>标签中的同步代码,属于第一个宏任务)
  • I/O 操作(文件读取、网络请求等异步操作)

2. 微任务(Microtask)

微任务是优先级更高的小型异步任务,会在当前宏任务执行完毕后、下一个宏任务执行前,被批量清空(按顺序执行所有微任务)。

常见微任务类型:

  • Promise.then()/Promise.catch()/Promise.finally()(Promise 状态改变后的回调)
  • async/awaitawait后面的代码(本质是 Promise.then 的语法糖,属于微任务)
  • process.nextTick(Node.js 环境,优先级高于普通微任务)
  • MutationObserver(浏览器环境,监听 DOM 变化的回调)

二、核心执行机制(事件循环 Event Loop 关键规则)

  1. 先执行当前宏任务中的同步代码,按代码书写顺序依次执行;
  2. 同步代码执行过程中,遇到异步任务会分别归类到「宏任务队列」或「微任务队列」;
  3. 当当前宏任务的同步代码执行完毕后,立即清空微任务队列:按微任务添加顺序,依次执行所有微任务(若微任务执行过程中产生新的微任务,仍会加入当前微任务队列,一并清空);
  4. 微任务队列清空后,执行一次 UI 渲染(浏览器环境)/ 收尾操作(Node.js 环境);
  5. 从宏任务队列中取出下一个优先级最高的宏任务执行,重复步骤 1-4,形成事件循环。

一句话总结:宏任务按队列排队执行,每个宏任务执行后,必先清空微任务队列,再执行下一个宏任务(微任务优先级 > 宏任务)

三、关键知识点:async/await 的执行逻辑

async/await是 Promise 的语法糖,其执行顺序需重点掌握:

  1. async修饰的函数,会返回一个 Promise 实例,函数内部同步代码正常执行;
  2. await关键字后面的表达式,会先同步执行(获取表达式结果);
  3. await会暂停当前函数的执行,将await后面的代码(即 “后续逻辑”)加入微任务队列(而非同步执行);
  4. await后面的 Promise 状态变为fulfilled后,微任务队列中的 “后续逻辑” 才会执行。

示例验证:

async function testAsync() { console.log("1. await 前的同步代码"); await Promise.resolve("await 结果"); // await 后面的表达式同步执行 console.log("2. await 后的代码(微任务)"); } console.log("0. 全局同步代码"); testAsync(); console.log("3. 全局同步代码后续"); // 执行结果顺序:0 → 1 → 3 → 2 // 解析: // 1. 先执行全局同步代码:输出 0; // 2. 调用 testAsync,执行内部同步代码:输出 1; // 3. 执行 await 后面的 Promise.resolve(同步),然后将 console.log("2...") 加入微任务队列; // 4. 退出 testAsync,继续执行全局同步代码:输出 3; // 5. 当前宏任务(全局脚本)同步代码执行完毕,清空微任务队列:输出 2。

四、经典面试题解析(覆盖 setTimeout/Promise/async/await)

面试题 1:基础版(setTimeout + Promise.then)

console.log("1. 全局同步代码"); setTimeout(() => { console.log("4. setTimeout 宏任务"); }, 0); Promise.resolve() .then(() => { console.log("3. Promise.then 微任务"); }); console.log("2. 全局同步代码后续");
执行结果顺序
1. 全局同步代码 2. 全局同步代码后续 3. Promise.then 微任务 4. setTimeout 宏任务
解析
  1. 执行当前宏任务(全局脚本)的同步代码:先输出 1,再输出 2;
  2. 同步执行过程中:
    • setTimeout回调被加入「宏任务队列」;
    • Promise.resolve()立即变为 fulfilled 状态,then回调被加入「微任务队列」;
  3. 全局同步代码执行完毕,清空微任务队列:输出 3;
  4. 微任务队列清空,取出宏任务队列中的setTimeout回调执行:输出 4。

面试题 2:进阶版(setTimeout + Promise + async/await)

console.log("1. 全局同步代码"); setTimeout(() => { console.log("6. setTimeout 宏任务1"); Promise.resolve().then(() => { console.log("7. setTimeout 内部的微任务"); }); }, 0); setTimeout(() => { console.log("8. setTimeout 宏任务2"); }, 0); async function asyncFunc() { console.log("2. async 内部同步代码"); await Promise.resolve(); console.log("5. await 后的微任务"); } asyncFunc(); Promise.resolve() .then(() => { console.log("4. 全局 Promise 微任务"); }); console.log("3. 全局同步代码后续");
执行结果顺序
1. 全局同步代码 2. async 内部同步代码 3. 全局同步代码后续 4. 全局 Promise 微任务 5. await 后的微任务 6. setTimeout 宏任务1 7. setTimeout 内部的微任务 8. setTimeout 宏任务2
解析
  1. 执行全局宏任务的同步代码:
    • 输出 1;
    • 调用asyncFunc,执行内部同步代码,输出 2;
    • await Promise.resolve()同步执行后,将console.log("5...")加入微任务队列;
    • Promise.resolve().then(...)console.log("4...")加入微任务队列;
    • 输出 3;
  2. 全局同步代码执行完毕,清空微任务队列(按添加顺序执行):
    • 先执行全局 Promise 的微任务,输出 4;
    • 再执行 async/await 的微任务,输出 5;
  3. 微任务队列清空,执行下一个宏任务(第一个 setTimeout):
    • 输出 6;
    • 其内部的Promise.then回调加入微任务队列,立即清空该微任务队列,输出 7;
  4. 第一个 setTimeout 宏任务执行完毕,执行下一个宏任务(第二个 setTimeout):输出 8。

面试题 3:高阶版(含微任务嵌套 + 宏任务优先级)

console.log("1. 全局同步代码"); async function async1() { await async2(); console.log("6. async1 await 后代码"); } async function async2() { console.log("2. async2 同步代码"); return Promise.resolve().then(() => { console.log("4. async2 返回的 Promise 微任务"); }); } async1(); Promise.resolve() .then(() => { console.log("5. 全局 Promise 微任务"); }); setTimeout(() => { console.log("7. setTimeout 宏任务"); Promise.resolve().then(() => { console.log("8. setTimeout 内部微任务"); }); }, 0); console.log("3. 全局同步代码后续");
执行结果顺序
1. 全局同步代码 2. async2 同步代码 3. 全局同步代码后续 4. async2 返回的 Promise 微任务 5. 全局 Promise 微任务 6. async1 await 后代码 7. setTimeout 宏任务 8. setTimeout 内部微任务
解析
  1. 执行全局同步代码:输出 1 → 调用async1→ 执行await async2(),先调用async2
  2. 执行async2同步代码:输出 2 →return Promise.resolve().then(...),该then回调加入微任务队列;
  3. async2执行完毕,await等待其返回的 Promise 完成,将console.log("6...")加入微任务队列(需等async2的 Promise 微任务执行完才会触发);
  4. 继续执行全局同步代码:输出 3 → 全局Promise.then回调加入微任务队列;
  5. 全局同步代码执行完毕,清空微任务队列:
    • 先执行async2内部的 Promise 微任务:输出 4;
    • 再执行全局 Promise 微任务:输出 5;
    • 最后执行async1await 后的微任务:输出 6;
  6. 微任务队列清空,执行setTimeout宏任务:输出 7 → 其内部 Promise 微任务立即执行,输出 8。

五、总结(核心考点提炼)

  1. 队列优先级:微任务队列 > 宏任务队列(当前宏任务执行后,必先清空微任务);
  2. 微任务包含Promise.then/catch/finallyasync/await后续代码;
  3. 宏任务包含setTimeout/setInterval、全局脚本、I/O 操作;
  4. async/await 关键await后面的表达式同步执行,后续代码进入微任务队列;
  5. 执行流程口诀:同步代码先执行 → 微任务队列全清空 → 宏任务队列取一个 → 重复循环。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询