宏任务与微任务

宏任务(MacroTask)与微任务(MicroTask)是 JavaScript 异步执行机制中的核心概念,直接影响代码的执行顺序和性能。以下是从定义到实战的系统性解析:

一、核心定义与区别

1. 宏任务(MacroTask)
  • 定义:由浏览器或 Node.js 环境提供的异步任务,每次事件循环(Event Loop)开始时执行。
  • 常见类型
    • 浏览器环境setTimeoutsetIntervalsetImmediate(Node.js)、requestAnimationFrameUI渲染
    • Node.js 环境setTimeoutsetIntervalI/O操作(如文件读取)。
2. 微任务(MicroTask)
  • 定义:由 JavaScript 引擎内部提供的异步任务,在当前宏任务执行结束后、下一个宏任务开始前立即执行。
  • 常见类型
    • Promise.then()Promise.catch()Promise.finally()
    • MutationObserver(DOM 监听)
    • process.nextTick(Node.js,优先级高于其他微任务)

二、执行机制:事件循环中的优先级

  1. 事件循环(Event Loop)每次从宏任务队列中取一个任务执行。
  2. 宏任务执行完毕后,立即清空当前的微任务队列(所有微任务按顺序执行)。
  3. 微任务队列清空后,进行UI 渲染(如果有 DOM 变更)。
  4. 进入下一轮事件循环,重复上述步骤。

关键点:微任务的执行时机优先于下一个宏任务。

三、代码示例:理解执行顺序

javascript

console.log('1'); // 同步任务,立即执行

setTimeout(() => {
  console.log('2'); // 宏任务,放入宏任务队列
}, 0);

Promise.resolve().then(() => {
  console.log('3'); // 微任务,放入微任务队列
});

console.log('4'); // 同步任务,立即执行

// 输出顺序:1 → 4 → 3 → 2

执行流程解析

  1. 同步代码console.log('1')console.log('4')依次执行。
  2. setTimeout的回调被放入宏任务队列。
  3. Promise.then的回调被放入微任务队列。
  4. 同步代码执行完毕后,检查微任务队列,执行console.log('3')
  5. 微任务队列清空后,进入下一轮事件循环,执行宏任务队列中的console.log('2')

四、进阶示例:复杂嵌套场景

javascript

console.log('start');

setTimeout(() => {
  console.log('setTimeout1');
  Promise.resolve().then(() => console.log('promise1'));
}, 0);

Promise.resolve().then(() => {
  console.log('promise2');
  setTimeout(() => console.log('setTimeout2'), 0);
});

console.log('end');

// 输出顺序:start → end → promise2 → setTimeout1 → promise1 → setTimeout2

执行流程解析

  1. 同步代码console.log('start')console.log('end')执行。
  2. 第一个setTimeout的回调放入宏任务队列。
  3. Promise.then的回调放入微任务队列,当前宏任务结束后执行console.log('promise2')
  4. 微任务中第二个setTimeout的回调放入宏任务队列。
  5. 进入下一轮事件循环,执行第一个宏任务console.log('setTimeout1')
  6. 第一个宏任务结束后,检查微任务队列,执行console.log('promise1')
  7. 进入下一轮事件循环,执行第二个宏任务console.log('setTimeout2')

五、性能优化中的应用

1. 批量 DOM 操作
  • 问题:频繁操作 DOM 会触发多次重排(reflow)和重绘(repaint)。
  • 优化:使用微任务批量处理 DOM 变更。

javascript

// 错误示例:多次触发重排
document.body.append(el1);
document.body.append(el2);
document.body.append(el3);

// 优化示例:用微任务批量处理
Promise.resolve().then(() => {
  document.body.append(el1);
  document.body.append(el2);
  document.body.append(el3);
}); // 仅触发一次重排
2. 避免 UI 卡顿
  • 反例:在宏任务中执行大量计算,阻塞 UI 渲染。

javascript

// 阻塞主线程2秒,页面无响应
setTimeout(() => {
  let sum = 0;
  for (let i = 0; i < 1e9; i++) sum += i;
  console.log(sum);
}, 0);

  • 优化:用微任务分段执行,保持页面响应性。

javascript

function heavyTask() {
  let sum = 0;
  const batchSize = 1e6;
  let i = 0;

  function processBatch() {
    for (let j = 0; j < batchSize && i < 1e9; j++, i++) {
      sum += i;
    }
    if (i < 1e9) {
      Promise.resolve().then(processBatch); // 用微任务继续执行
    } else {
      console.log(sum);
    }
  }

  processBatch();
}

六、Node.js 与浏览器的差异

特性浏览器环境Node.js 环境
微任务队列所有微任务按注册顺序执行。process.nextTick优先于其他微任务(如Promise.then)。
宏任务类型包含requestAnimationFrame、UI 渲染等。包含setImmediateI/O回调等。
事件循环阶段无明确阶段划分。分为timersI/O callbacksidle, prepare等阶段。

七、高频面试考点

  1. 手写执行顺序题
    给出包含宏任务和微任务的代码,要求写出输出顺序。关键是记住:

    • 同步代码优先执行
    • 微任务在当前宏任务结束后立即执行
    • 宏任务在下一轮事件循环开始时执行
  2. 微任务的典型应用

    • 实现Promise的链式调用
    • Vue 3的响应式更新机制(queueMicrotask
  3. 性能优化

    • 用微任务合并 DOM 操作,减少重排重绘
    • 避免在微任务中执行大量计算,防止阻塞 UI 渲染

八、一句话总结

  • 宏任务:异步操作的 “大块头”,每次事件循环开始时执行(如定时器、网络请求)。
  • 微任务:异步操作的 “插队者”,在当前任务结束后立即执行(如 Promise 回调)。

合理利用宏微任务的执行顺序,是写出高性能、无卡顿前端应用的关键。

### 任务任务的概念及区别 在 JavaScript 中,任务(Macrotask)任务(Microtask)是事件循环(Event Loop)机制中的两个核心概念。它们的定义、执行顺序以及应用场景存在显著差异。 #### 任务 任务是指那些被推入任务队列的任务,通常涉及 I/O 操作或定时器等耗时操作。一个典型的例子是 `setTimeout` `setInterval` 函数[^1]。当这些函数被调用时,它们会被加入到任务队列中,并等待当前主线程上的代码执行完毕后按顺序执行。此外,每个完整的 JavaScript 脚本也是一个任务[^2]。 #### 任务 任务则是指那些被推入任务队列的任务,它们的优先级高于任务。常见的任务包括 `Promise.then`、`MutationObserver` 等[^3]。任务会在当前任务执行完毕后立即执行,这意味着所有任务都会在下一个任务开始之前完成执行[^4]。 #### 区别 - **执行优先级**:任务的优先级高于任务。每次执行完一个任务后,JavaScript 引擎会检查任务队列并依次执行其中的所有任务,直到队列为空。 - **执行时机**:任务的执行依赖于事件循环的轮次,而任务则会在当前任务结束后立即执行。 - **典型场景**: - 任务:`setTimeout`、`setInterval`、`setImmediate`(Node.js 环境)、`I/O` 操作等。 - 任务:`Promise.then`、`MutationObserver`、`process.nextTick`(Node.js 环境)等。 ### 执行顺序 JavaScript 的事件循环机制决定了任务任务的执行顺序。以下是其基本流程: 1. 主线程首先执行同步代码,这是第一阶段。 2. 当同步代码执行完毕后,JavaScript 引擎会检查任务队列,并依次执行其中的所有任务。 3. 任务队列清空后,事件循环进入下一阶段,从任务队列中取出第一个任务并执行。 4. 每个任务执行完毕后,都会再次检查任务队列并执行其中的任务。 5. 这一过程不断重复,直到所有任务队列都被清空。 以下是一个简单的代码示例,展示任务任务的执行顺序: ```javascript console.log('Script start'); setTimeout(() => { console.log('Timeout'); }, 0); Promise.resolve().then(() => { console.log('Promise 1'); }); Promise.resolve().then(() => { console.log('Promise 2'); }); console.log('Script end'); ``` 输出结果为: ``` Script start Script end Promise 1 Promise 2 Timeout ``` 解释: - 同步代码 `console.log('Script start')` `console.log('Script end')` 首先被执行。 - 接着,任务队列中的两个 `Promise.then` 被依次执行。 - 最后,任务队列中的 `setTimeout` 回调被执行[^5]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值