博客 JavaScript

深入理解 JavaScript 事件循环

2023年12月28日
Geek Developer
0 次阅读
15 分钟

深入理解 JavaScript 事件循环

事件循环是 JavaScript 异步编程的核心机制。理解它对于编写高效的异步代码至关重要。

执行栈(Call Stack)

JavaScript 是单线程的,所有的代码都在执行栈中运行:

function first() {
  console.log('First')
  second()
}

function second() {
  console.log('Second')
}

first()
// First
// Second

任务队列(Task Queue)

宏任务(Macro Tasks)

  • setTimeout
  • setInterval
  • setImmediate (Node.js)
  • I/O 操作

微任务(Micro Tasks)

  • Promise.then/catch/finally
  • MutationObserver
  • queueMicrotask

事件循环的执行顺序

  1. 执行同步代码
  2. 清空微任务队列
  3. 执行一个宏任务
  4. 清空微任务队列
  5. 重复步骤3-4
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

async/await 与事件循环

async/await 是 Promise 的语法糖:

async function example() {
  console.log('1')

  await Promise.resolve()
  console.log('2')  // 相当于 .then(() => console.log('2'))

  await Promise.resolve()
  console.log('3')
}

example()
console.log('4')

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

实战应用

优化长任务

将长任务分割成多个小任务:

async function processLargeArray(array) {
  for (let i = 0; i < array.length; i++) {
    processItem(array[i])

    // 每处理100个项目就让出控制权
    if (i % 100 === 0) {
      await new Promise(resolve => setTimeout(resolve, 0))
    }
  }
}

防止阻塞

使用 requestIdleCallback 在浏览器空闲时执行任务:

function doHeavyWork() {
  requestIdleCallback((deadline) => {
    while (deadline.timeRemaining() > 0 && tasks.length > 0) {
      performTask(tasks.pop())
    }

    if (tasks.length > 0) {
      doHeavyWork()
    }
  })
}

总结

理解事件循环对于编写高效的 JavaScript 代码至关重要。记住:

  • 同步代码优先执行
  • 微任务优先于宏任务
  • 合理使用异步避免阻塞