icon
事件循环

事件循环

事件循环(Event Loop)是 JavaScript 处理异步操作的机制。JavaScript 是单线程的,通过事件循环实现非阻塞的异步执行。

基本概念

JavaScript 执行环境包含:

  • 调用栈(Call Stack):执行同步代码
  • 任务队列(Task Queue):存放异步任务回调
  • 微任务队列(Microtask Queue):存放 Promise、MutationObserver 等

执行顺序

  1. 执行同步代码
  2. 执行完同步代码后,检查微任务队列
  3. 执行所有微任务
  4. 执行一个宏任务
  5. 再次检查微任务队列
  6. 重复步骤 3-5
console.log('1'); // 同步

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

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

console.log('4'); // 同步

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

宏任务(Macro Task)

  • setTimeout
  • setInterval
  • setImmediate(Node.js)
  • I/O 操作
  • UI 渲染
console.log('start');

setTimeout(() => {
  console.log('setTimeout');
}, 0);

console.log('end');

// 输出:start, end, setTimeout

微任务(Micro Task)

  • Promise.then/catch/finally
  • queueMicrotask
  • MutationObserver
  • process.nextTick(Node.js,优先级最高)
console.log('1');

Promise.resolve().then(() => {
  console.log('2');
});

queueMicrotask(() => {
  console.log('3');
});

console.log('4');

// 输出:1, 4, 2, 3

执行顺序示例

console.log('1');

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

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

console.log('6');

// 输出顺序:1, 6, 4, 2, 3, 5
// 解释:
// 1. 同步代码:1, 6
// 2. 微任务:4(执行时又添加了 setTimeout 宏任务)
// 3. 宏任务:2(执行时又添加了 Promise 微任务)
// 4. 微任务:3
// 5. 宏任务:5

async/await

async/await 基于 Promise,await 后面的代码会被包装成微任务:

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}

async function async2() {
  console.log('async2');
}

console.log('script start');

setTimeout(() => {
  console.log('setTimeout');
}, 0);

async1();

new Promise(function(resolve) {
  console.log('promise1');
  resolve();
}).then(function() {
  console.log('promise2');
});

console.log('script end');

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

Node.js 事件循环

Node.js 的事件循环包含多个阶段:

  1. timers:执行 setTimeout 和 setInterval 回调
  2. pending callbacks:执行延迟到下一个循环迭代的 I/O 回调
  3. idle, prepare:内部使用
  4. poll:获取新的 I/O 事件
  5. check:执行 setImmediate 回调
  6. close callbacks:执行关闭回调
// Node.js 中的执行顺序
console.log('1');

setTimeout(() => console.log('2'), 0);
setImmediate(() => console.log('3'));

process.nextTick(() => console.log('4'));

Promise.resolve().then(() => console.log('5'));

console.log('6');

// 输出:1, 6, 4, 5, 2, 3
// process.nextTick 优先级最高

实际应用

  1. 避免阻塞主线程
// 将耗时操作分解为多个微任务
function processLargeArray(array) {
  return new Promise(resolve => {
    let index = 0;
    
    function processChunk() {
      const chunkSize = 1000;
      const end = Math.min(index + chunkSize, array.length);
      
      while (index < end) {
        // 处理数据
        processItem(array[index]);
        index++;
      }
      
      if (index < array.length) {
        // 使用微任务继续处理
        Promise.resolve().then(processChunk);
      } else {
        resolve();
      }
    }
    
    processChunk();
  });
}
  1. 优先级控制
// 使用微任务确保高优先级
function highPriorityTask() {
  Promise.resolve().then(() => {
    console.log('高优先级任务');
  });
}

function lowPriorityTask() {
  setTimeout(() => {
    console.log('低优先级任务');
  }, 0);
}

highPriorityTask();
lowPriorityTask();
// 高优先级任务先执行
  1. 批量更新
let updateQueue = [];
let isUpdating = false;

function scheduleUpdate(update) {
  updateQueue.push(update);
  
  if (!isUpdating) {
    isUpdating = true;
    Promise.resolve().then(() => {
      // 批量处理更新
      updateQueue.forEach(update => update());
      updateQueue = [];
      isUpdating = false;
    });
  }
}

注意事项

  1. 避免长时间运行的同步代码
// 不好的做法
function badLoop() {
  for (let i = 0; i < 10000000; i++) {
    // 阻塞主线程
  }
}

// 好的做法:使用异步
async function goodLoop() {
  for (let i = 0; i < 10000000; i++) {
    if (i % 1000 === 0) {
      await new Promise(resolve => setTimeout(resolve, 0));
    }
  }
}
  1. 理解执行时机
// DOM 更新在微任务之后
button.addEventListener('click', () => {
  Promise.resolve().then(() => {
    console.log('微任务');
  });
  console.log('同步');
});

// 输出:同步, 微任务
// DOM 更新发生在微任务之后