阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 宏任务与微任务

宏任务与微任务

作者:陈川 阅读数:14572人阅读 分类: Node.js

宏任务与微任务的概念

宏任务和微任务是JavaScript中任务队列的两种类型,它们决定了代码的执行顺序。宏任务包括script整体代码、setTimeout、setInterval、I/O操作、UI渲染等;微任务则包括Promise.then、process.nextTick、MutationObserver等。理解它们的区别对于掌握JavaScript异步编程至关重要。

事件循环中的执行顺序

JavaScript引擎在执行代码时遵循特定顺序:

  1. 执行一个宏任务(通常是script整体代码)
  2. 执行过程中遇到微任务就加入微任务队列
  3. 宏任务执行完毕后立即执行所有微任务
  4. 进行UI渲染(浏览器环境)
  5. 开始下一个宏任务
console.log('script start'); // 宏任务

setTimeout(() => {
  console.log('setTimeout'); // 宏任务
}, 0);

Promise.resolve().then(() => {
  console.log('promise1'); // 微任务
}).then(() => {
  console.log('promise2'); // 微任务
});

console.log('script end'); // 宏任务

// 输出顺序:
// script start
// script end
// promise1
// promise2
// setTimeout

Node.js中的process.nextTick

在Node.js环境中,process.nextTick创建的微任务优先级高于Promise.then:

Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));

// 输出顺序:
// nextTick
// promise

宏任务与微任务的嵌套

当微任务中产生新的微任务时,会继续执行直到微任务队列清空:

function microtaskLoop() {
  Promise.resolve().then(() => {
    console.log('microtask');
    microtaskLoop(); // 无限循环
  });
}

microtaskLoop();
setTimeout(() => console.log('timeout'), 0);

// 会不断输出'microtask',永远不会执行timeout

浏览器与Node.js的差异

不同环境下宏任务和微任务的实现有差异:

  1. 浏览器环境

    • 宏任务:setTimeout、setInterval、requestAnimationFrame、I/O、UI渲染
    • 微任务:Promise.then、MutationObserver
  2. Node.js环境

    • 宏任务:setTimeout、setInterval、setImmediate、I/O
    • 微任务:process.nextTick、Promise.then
// Node.js中的执行顺序示例
setImmediate(() => console.log('immediate'));
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));

// 可能的输出顺序:
// nextTick
// promise
// timeout
// immediate

实际应用场景

  1. 批量DOM更新
// 使用微任务批量处理DOM更新
function batchUpdate() {
  let pending = false;
  const callbacks = [];
  
  return function(callback) {
    callbacks.push(callback);
    if (!pending) {
      pending = true;
      Promise.resolve().then(() => {
        const copies = callbacks.slice();
        callbacks.length = 0;
        pending = false;
        copies.forEach(cb => cb());
      });
    }
  };
}

const update = batchUpdate();
update(() => console.log('Update 1'));
update(() => console.log('Update 2'));
  1. 优先级控制
// 确保关键任务优先执行
function criticalTask() {
  process.nextTick(() => {
    console.log('Critical task');
  });
}

function normalTask() {
  Promise.resolve().then(() => {
    console.log('Normal task');
  });
}

criticalTask();
normalTask();
// 输出:
// Critical task
// Normal task

性能考量

  1. 微任务堆积:过多的微任务会导致主线程阻塞
  2. 任务拆分:长时间运行的任务应该拆分为多个宏任务
  3. 优先级选择:根据场景选择合适的任务类型
// 不好的实践 - 微任务无限循环
function badPractice() {
  Promise.resolve().then(badPractice);
}

// 好的实践 - 使用宏任务拆分
function goodPractice() {
  // 处理一部分工作
  if (moreWork) {
    setTimeout(goodPractice, 0);
  }
}

常见误区

  1. 认为setTimeout(fn, 0)会立即执行:实际上它只是尽快加入宏任务队列
  2. 忽略微任务的优先级:微任务会在下一个宏任务之前全部执行完毕
  3. 混淆Node.js和浏览器的实现差异:特别是process.nextTick和setImmediate的行为
// 误区示例
console.log('start');

setTimeout(() => {
  console.log('timeout 1');
  Promise.resolve().then(() => console.log('promise 1'));
}, 0);

setTimeout(() => {
  console.log('timeout 2');
  Promise.resolve().then(() => console.log('promise 2'));
}, 0);

console.log('end');

// 实际输出:
// start
// end
// timeout 1
// promise 1
// timeout 2
// promise 2

高级应用模式

  1. 任务调度器
class TaskScheduler {
  constructor() {
    this.macroTasks = [];
    this.microTasks = [];
    this.isProcessing = false;
  }

  addMacroTask(task) {
    this.macroTasks.push(task);
    this.schedule();
  }

  addMicroTask(task) {
    this.microTasks.push(task);
    this.schedule();
  }

  schedule() {
    if (this.isProcessing) return;
    
    this.isProcessing = true;
    Promise.resolve().then(() => {
      // 先执行所有微任务
      while (this.microTasks.length) {
        const task = this.microTasks.shift();
        task();
      }
      
      // 然后执行一个宏任务
      if (this.macroTasks.length) {
        const task = this.macroTasks.shift();
        task();
      }
      
      this.isProcessing = false;
      if (this.microTasks.length || this.macroTasks.length) {
        this.schedule();
      }
    });
  }
}
  1. 异步队列控制
async function processTasks(tasks, concurrency = 4) {
  const results = [];
  let index = 0;
  
  async function runNext() {
    if (index >= tasks.length) return;
    const currentIndex = index++;
    const task = tasks[currentIndex];
    results[currentIndex] = await task();
    await Promise.resolve(); // 微任务点
    await runNext();
  }
  
  const workers = Array(concurrency).fill().map(runNext);
  await Promise.all(workers);
  return results;
}

调试技巧

  1. 使用console.log定位执行顺序
  2. 利用Chrome DevTools的Performance面板
  3. Node.js中使用--trace-event-categories参数
// 调试示例
function debugFlow() {
  console.log('同步代码开始');
  
  setTimeout(() => {
    console.log('宏任务1');
    Promise.resolve().then(() => console.log('宏任务1的微任务'));
  }, 0);
  
  Promise.resolve().then(() => {
    console.log('微任务1');
    setTimeout(() => console.log('微任务1的宏任务'), 0);
  });
  
  console.log('同步代码结束');
}

debugFlow();

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌