JavaScript中的Event Loop与宏任务/微任务
字数 1233 2025-11-04 20:48:21

JavaScript中的Event Loop与宏任务/微任务

描述
Event Loop(事件循环)是JavaScript实现异步编程的核心机制,负责协调调用栈、Web API、任务队列之间的工作。宏任务(MacroTask)和微任务(MicroTask)是任务队列中的两种类型,它们的执行优先级和时机直接影响异步代码的顺序。理解二者的区别能避免实际开发中的执行顺序陷阱。

解题过程

  1. 基本概念

    • 调用栈(Call Stack):同步代码按顺序执行的地方,当函数被调用时入栈,执行完毕出栈。
    • Web API:浏览器提供的异步功能(如setTimeoutAJAX),由其他线程处理,完成后将回调函数推入任务队列。
    • 事件循环:持续检查调用栈是否为空,若为空则从任务队列中取出第一个任务推入调用栈执行。
  2. 宏任务与微任务的定义

    • 宏任务:包括整体脚本、setTimeoutsetInterval、I/O操作、UI渲染等。
    • 微任务:包括Promise.then/catch/finallyMutationObserverqueueMicrotask等。
    • 关键规则
      • 每执行一个宏任务后,会清空当前微任务队列中的所有任务。
      • 微任务优先级高于宏任务。
  3. 执行流程示例分析
    通过代码逐步演示执行顺序:

    console.log("1"); // 同步代码,直接输出
    
    setTimeout(() => {
      console.log("2"); // 宏任务,进入宏任务队列
    }, 0);
    
    Promise.resolve().then(() => {
      console.log("3"); // 微任务,进入微任务队列
    });
    
    console.log("4"); // 同步代码,直接输出
    

    步骤分解

    • 同步阶段
      1. 输出1 → 调用栈执行console.log("1")后清空。
      2. 遇到setTimeout,将其回调函数交给Web API计时(0ms后放入宏任务队列)。
      3. 遇到Promise.resolve().then,将回调函数放入微任务队列。
      4. 输出4 → 调用栈清空。
    • 微任务阶段
      调用栈为空后,事件循环先检查微任务队列,发现Promise回调,输出3
    • 宏任务阶段
      微任务队列清空后,从宏任务队列中取出setTimeout回调,输出2
      最终输出顺序1 → 4 → 3 → 2
  4. 嵌套场景下的复杂案例

    setTimeout(() => console.log("宏任务1"), 0);
    
    Promise.resolve().then(() => {
      console.log("微任务1");
      Promise.resolve().then(() => console.log("微任务2"));
    });
    
    queueMicrotask(() => console.log("微任务3"));
    

    执行顺序

    • 同步代码执行:注册宏任务1到队列,注册Promise.then(微任务1)到微任务队列,注册queueMicrotask(微任务3)到微任务队列。
    • 清空微任务队列:按注册顺序依次执行微任务1 → 输出后嵌套的Promise.then(微任务2)被加入当前微任务队列,继续清空微任务队列(微任务3 → 微任务2)。
    • 最后执行宏任务1。
      输出顺序微任务1 → 微任务3 → 微任务2 → 宏任务1
  5. 总结与注意事项

    • 微任务队列在每个宏任务执行后清空,且期间新产生的微任务会继续加入当前队列并执行(递归清空)。
    • 宏任务按事件循环的轮次执行,例如setTimeout的延迟是相对于当前宏任务完成后的最小时间。
    • 常见误区:setTimeout(fn, 0)并非立即执行,而是等待当前同步代码及微任务队列清空后才会执行。
JavaScript中的Event Loop与宏任务/微任务 描述 Event Loop(事件循环)是JavaScript实现异步编程的核心机制,负责协调调用栈、Web API、任务队列之间的工作。宏任务(MacroTask)和微任务(MicroTask)是任务队列中的两种类型,它们的执行优先级和时机直接影响异步代码的顺序。理解二者的区别能避免实际开发中的执行顺序陷阱。 解题过程 基本概念 调用栈(Call Stack) :同步代码按顺序执行的地方,当函数被调用时入栈,执行完毕出栈。 Web API :浏览器提供的异步功能(如 setTimeout 、 AJAX ),由其他线程处理,完成后将回调函数推入任务队列。 事件循环 :持续检查调用栈是否为空,若为空则从任务队列中取出第一个任务推入调用栈执行。 宏任务与微任务的定义 宏任务 :包括整体脚本、 setTimeout 、 setInterval 、I/O操作、UI渲染等。 微任务 :包括 Promise.then/catch/finally 、 MutationObserver 、 queueMicrotask 等。 关键规则 : 每执行一个 宏任务 后,会清空当前 微任务队列 中的所有任务。 微任务优先级高于宏任务。 执行流程示例分析 通过代码逐步演示执行顺序: 步骤分解 : 同步阶段 : 输出 1 → 调用栈执行 console.log("1") 后清空。 遇到 setTimeout ,将其回调函数交给Web API计时(0ms后放入宏任务队列)。 遇到 Promise.resolve().then ,将回调函数放入微任务队列。 输出 4 → 调用栈清空。 微任务阶段 : 调用栈为空后,事件循环先检查微任务队列,发现 Promise 回调,输出 3 。 宏任务阶段 : 微任务队列清空后,从宏任务队列中取出 setTimeout 回调,输出 2 。 最终输出顺序 : 1 → 4 → 3 → 2 。 嵌套场景下的复杂案例 执行顺序 : 同步代码执行:注册宏任务1到队列,注册 Promise.then (微任务1)到微任务队列,注册 queueMicrotask (微任务3)到微任务队列。 清空微任务队列:按注册顺序依次执行微任务1 → 输出后嵌套的 Promise.then (微任务2)被加入当前微任务队列,继续清空微任务队列(微任务3 → 微任务2)。 最后执行宏任务1。 输出顺序 : 微任务1 → 微任务3 → 微任务2 → 宏任务1 。 总结与注意事项 微任务队列在 每个宏任务执行后 清空,且期间新产生的微任务会继续加入当前队列并执行(递归清空)。 宏任务按事件循环的轮次执行,例如 setTimeout 的延迟是相对于当前宏任务完成后的最小时间。 常见误区: setTimeout(fn, 0) 并非立即执行,而是等待当前同步代码及微任务队列清空后才会执行。