Skip to content

JavaScript 事件循环

什么是事件循环

事件循环(Event Loop)是 JavaScript 中处理异步操作的机制,它负责协调代码的执行、事件的触发、网络请求等异步任务的处理。

JavaScript 是单线程的,这意味着它一次只能执行一个任务。为了处理异步操作(如网络请求、定时器、DOM 事件等),JavaScript 引入了事件循环机制,使得这些异步操作可以在后台处理,而不会阻塞主线程的执行。

事件循环的基本概念

1. 调用栈(Call Stack)

调用栈是 JavaScript 中用于跟踪函数调用的结构,它遵循后进先出(LIFO)的原则。当执行一个函数时,它会被推入栈顶;当函数执行完毕时,它会被弹出栈。

javascript
// 示例
function foo() {
  console.log("foo");
  bar();
}

function bar() {
  console.log("bar");
}

foo();

// 调用栈的变化:
// 1. 推入 foo
// 2. 执行 foo,输出 'foo'
// 3. 推入 bar
// 4. 执行 bar,输出 'bar'
// 5. 弹出 bar
// 6. 弹出 foo

2. 消息队列(Message Queue)

消息队列(也称为任务队列)用于存储待处理的异步任务。当异步任务完成时,它会被添加到消息队列中。

3. 微任务队列(Microtask Queue)

微任务队列用于存储优先级较高的异步任务,如 Promise 的回调函数、MutationObserver 的回调函数等。

4. 事件循环的执行流程

事件循环的基本执行流程如下:

  1. 执行调用栈中的同步任务,直到栈为空
  2. 执行微任务队列中的所有任务
  3. 从消息队列中取出一个任务执行
  4. 重复步骤 1-3
javascript
// 示例
console.log("开始");

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

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

console.log("结束");

// 输出顺序:
// 开始
// 结束
// Promise
// setTimeout

// 执行流程:
// 1. 执行 console.log('开始')
// 2. 将 setTimeout 回调添加到消息队列
// 3. 将 Promise 回调添加到微任务队列
// 4. 执行 console.log('结束')
// 5. 执行微任务队列中的 Promise 回调,输出 'Promise'
// 6. 从消息队列中取出 setTimeout 回调执行,输出 'setTimeout'

宏任务和微任务

1. 宏任务(Macro Task)

宏任务是指需要经过事件循环才能执行的任务,包括:

  • setTimeout
  • setInterval
  • setImmediate(Node.js)
  • I/O 操作
  • DOM 事件
  • requestAnimationFrame
  • 页面渲染

2. 微任务(Micro Task)

微任务是指在当前任务执行完毕后立即执行的任务,包括:

  • Promisethencatchfinally 回调
  • async/await 中的异步操作
  • process.nextTick(Node.js)
  • MutationObserver 回调

3. 执行顺序

事件循环中,宏任务和微任务的执行顺序如下:

  1. 执行当前宏任务中的同步代码
  2. 执行所有微任务
  3. 渲染页面(如果需要)
  4. 从消息队列中取出下一个宏任务执行
javascript
// 示例
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. 执行同步代码:console.log('1'), console.log('6')
// 2. 执行微任务:console.log('4'),并将 setTimeout 添加到消息队列
// 3. 从消息队列中取出第一个宏任务:console.log('2')
// 4. 执行微任务:console.log('3')
// 5. 从消息队列中取出下一个宏任务:console.log('5')

事件循环的工作原理

1. 调用栈的工作原理

调用栈是一个后进先出(LIFO)的数据结构,用于跟踪函数的调用。当执行一个函数时,它会被推入栈顶;当函数执行完毕时,它会被弹出栈。

javascript
// 示例
function a() {
  console.log("a 开始");
  b();
  console.log("a 结束");
}

function b() {
  console.log("b 开始");
  c();
  console.log("b 结束");
}

function c() {
  console.log("c 开始");
  console.log("c 结束");
}

a();

// 输出顺序:
// a 开始
// b 开始
// c 开始
// c 结束
// b 结束
// a 结束

// 调用栈的变化:
// 1. 推入 a
// 2. 执行 a 中的 console.log('a 开始')
// 3. 推入 b
// 4. 执行 b 中的 console.log('b 开始')
// 5. 推入 c
// 6. 执行 c 中的 console.log('c 开始') 和 console.log('c 结束')
// 7. 弹出 c
// 8. 执行 b 中的 console.log('b 结束')
// 9. 弹出 b
// 10. 执行 a 中的 console.log('a 结束')
// 11. 弹出 a

2. 异步任务的处理

当遇到异步任务时,JavaScript 会将其交给相应的 Web API(在浏览器中)或 Node.js API(在 Node.js 中)处理,而不会阻塞主线程的执行。

当异步任务完成时,它会将回调函数添加到消息队列(宏任务)或微任务队列中。

javascript
// 示例
console.log("开始");

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

// 异步任务(微任务)
Promise.resolve().then(() => {
  console.log("Promise");
});

console.log("结束");

// 输出顺序:
// 开始
// 结束
// Promise
// setTimeout

// 执行流程:
// 1. 执行 console.log('开始')
// 2. 遇到 setTimeout,将其交给 Web API 处理
// 3. 遇到 Promise,将其交给 Web API 处理
// 4. 执行 console.log('结束')
// 5. 调用栈为空,执行微任务队列中的 Promise 回调
// 6. 微任务队列为空,从消息队列中取出 setTimeout 回调执行

3. 事件循环的迭代

事件循环不断地重复以下步骤:

  1. 检查调用栈是否为空
  2. 如果为空,执行所有微任务
  3. 从消息队列中取出一个宏任务执行
  4. 重复上述步骤
javascript
// 示例
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:
//   执行同步代码:console.log('1'), console.log('6')
//   执行微任务:console.log('4'),并将 setTimeout 添加到消息队列
//   微任务队列为空
// 迭代 2:
//   从消息队列中取出第一个宏任务:console.log('2')
//   执行微任务:console.log('3')
//   微任务队列为空
// 迭代 3:
//   从消息队列中取出下一个宏任务:console.log('5')
//   微任务队列为空

浏览器中的事件循环

1. 浏览器的事件循环模型

浏览器中的事件循环由以下部分组成:

  • 调用栈:执行同步代码和处理函数调用
  • Web API:处理异步任务(如 setTimeout、XMLHttpRequest、DOM 事件等)
  • 消息队列:存储宏任务的回调函数
  • 微任务队列:存储微任务的回调函数
  • 渲染线程:负责页面的渲染

2. 渲染流程

在浏览器的事件循环中,页面渲染通常发生在微任务执行完毕后,下一个宏任务执行之前。

渲染流程包括:

  • 计算样式
  • 布局
  • 绘制
javascript
// 示例
console.log("开始");

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

// 微任务
Promise.resolve().then(() => {
  console.log("Promise");
  // 微任务执行完毕后会进行渲染
});

console.log("结束");

// 执行流程:
// 1. 执行同步代码
// 2. 执行微任务
// 3. 渲染页面
// 4. 执行宏任务

3. requestAnimationFrame

requestAnimationFrame 是浏览器提供的 API,用于在下次渲染之前执行回调函数,通常用于动画效果。

requestAnimationFrame 的回调函数会在渲染之前执行,它的执行时机在微任务之后,宏任务之前。

javascript
// 示例
console.log("1");

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

Promise.resolve().then(() => {
  console.log("3");
});

requestAnimationFrame(() => {
  console.log("4");
});

console.log("5");

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

// 执行流程:
// 1. 执行同步代码:console.log('1'), console.log('5')
// 2. 执行微任务:console.log('3')
// 3. 执行 requestAnimationFrame 回调:console.log('4')
// 4. 渲染页面
// 5. 执行宏任务:console.log('2')

Node.js 中的事件循环

1. Node.js 的事件循环模型

Node.js 的事件循环与浏览器的事件循环有所不同,它由以下 6 个阶段组成:

  1. timers:处理 setTimeoutsetInterval 的回调
  2. I/O callbacks:处理 I/O 操作的回调
  3. idle, prepare:内部使用
  4. poll:获取新的 I/O 事件,执行 I/O 相关的回调
  5. check:处理 setImmediate 的回调
  6. close callbacks:处理关闭事件的回调(如 socket.on('close', ...)

2. Node.js 中的宏任务和微任务

在 Node.js 中,宏任务和微任务的执行顺序如下:

  1. 执行当前阶段的任务
  2. 执行所有微任务
  3. 进入下一个阶段

3. process.nextTick

process.nextTick 是 Node.js 提供的 API,它会将回调函数添加到当前阶段的微任务队列的最前面,优先于其他微任务执行。

javascript
// 示例
console.log("1");

setTimeout(() => {
  console.log("2");
  process.nextTick(() => {
    console.log("3");
  });
  Promise.resolve().then(() => {
    console.log("4");
  });
}, 0);

process.nextTick(() => {
  console.log("5");
  Promise.resolve().then(() => {
    console.log("6");
  });
});

Promise.resolve().then(() => {
  console.log("7");
  process.nextTick(() => {
    console.log("8");
  });
});

console.log("9");

// 输出顺序:
// 1
// 9
// 5
// 7
// 8
// 6
// 2
// 3
// 4

// 执行流程:
// 1. 执行同步代码:console.log('1'), console.log('9')
// 2. 执行微任务(process.nextTick 优先):
//    - console.log('5'),并添加 Promise 到微任务队列
//    - console.log('7'),并添加 process.nextTick 到微任务队列
//    - console.log('8')
//    - console.log('6')
// 3. 进入 timers 阶段,执行 setTimeout 回调:console.log('2')
// 4. 执行微任务:
//    - console.log('3')
//    - console.log('4')

4. setImmediate vs setTimeout

在 Node.js 中,setImmediatesetTimeout(..., 0) 的执行顺序取决于它们被调用的位置:

  • 在主模块中,setTimeout(..., 0) 可能会先于 setImmediate 执行
  • 在 I/O 回调中,setImmediate 会先于 setTimeout(..., 0) 执行
javascript
// 示例 1:主模块中
console.log("开始");

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

setImmediate(() => {
  console.log("setImmediate");
});

console.log("结束");

// 可能的输出:
// 开始
// 结束
// setTimeout
// setImmediate

// 示例 2:I/O 回调中
const fs = require("fs");

console.log("开始");

fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log("setTimeout");
  }, 0);

  setImmediate(() => {
    console.log("setImmediate");
  });
});

console.log("结束");

// 输出:
// 开始
// 结束
// setImmediate
// setTimeout

事件循环的应用场景

1. 理解异步代码的执行顺序

事件循环机制可以帮助我们理解异步代码的执行顺序,特别是在处理多个异步任务时。

javascript
// 示例
console.log("1");

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

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

new Promise((resolve) => {
  console.log("5");
  resolve();
}).then(() => {
  console.log("6");
});

console.log("7");

// 输出顺序:
// 1
// 5
// 7
// 3
// 6
// 2
// 4

// 执行流程:
// 1. 执行同步代码:console.log('1'), console.log('5'), console.log('7')
// 2. 执行微任务:console.log('3')(添加 setTimeout 到消息队列), console.log('6')
// 3. 从消息队列中取出第一个宏任务:console.log('2')
// 4. 从消息队列中取出下一个宏任务:console.log('4')

2. 优化渲染性能

通过合理安排代码的执行时机,可以优化页面的渲染性能。

javascript
// 示例:优化渲染性能
function updateUI() {
  // 执行 DOM 更新
  console.log("更新 DOM");

  // 使用 requestAnimationFrame 在渲染之前执行动画
  requestAnimationFrame(() => {
    console.log("执行动画");
  });

  // 使用 Promise 在微任务中执行数据处理
  Promise.resolve().then(() => {
    console.log("处理数据");
  });
}

updateUI();

// 输出顺序:
// 更新 DOM
// 处理数据
// 执行动画

// 执行流程:
// 1. 执行 updateUI 函数
// 2. 执行同步代码:console.log('更新 DOM')
// 3. 执行微任务:console.log('处理数据')
// 4. 执行 requestAnimationFrame 回调:console.log('执行动画')
// 5. 渲染页面

3. 避免阻塞主线程

通过将耗时的操作放入异步任务中,可以避免阻塞主线程的执行。

javascript
// 示例:避免阻塞主线程
function processLargeData(data) {
  // 耗时的操作
  console.log("开始处理数据");

  // 将耗时操作放入微任务中
  return new Promise((resolve) => {
    // 模拟耗时操作
    setTimeout(() => {
      console.log("数据处理完成");
      resolve("处理结果");
    }, 1000);
  });
}

console.log("开始");

processLargeData().then((result) => {
  console.log("结果:", result);
});

console.log("继续执行其他任务");

// 输出顺序:
// 开始
// 开始处理数据
// 继续执行其他任务
// 数据处理完成
// 结果: 处理结果

4. 处理并发请求

事件循环机制可以帮助我们高效地处理并发请求。

javascript
// 示例:处理并发请求
function fetchData(url) {
  return fetch(url).then((response) => response.json());
}

async function fetchMultipleData() {
  console.log("开始请求数据");

  // 并发请求
  const [data1, data2, data3] = await Promise.all([
    fetchData("https://api.example.com/data1"),
    fetchData("https://api.example.com/data2"),
    fetchData("https://api.example.com/data3"),
  ]);

  console.log("所有数据请求完成");
  console.log("数据1:", data1);
  console.log("数据2:", data2);
  console.log("数据3:", data3);
}

fetchMultipleData();
console.log("继续执行");

// 输出顺序:
// 开始请求数据
// 继续执行
// 所有数据请求完成
// 数据1: {...}
// 数据2: {...}
// 数据3: {...}

事件循环的常见问题

1. 回调地狱

回调地狱(Callback Hell)是指多层嵌套的回调函数,使代码难以阅读和维护。

javascript
// 示例:回调地狱
setTimeout(() => {
  console.log("第一个回调");
  setTimeout(() => {
    console.log("第二个回调");
    setTimeout(() => {
      console.log("第三个回调");
      setTimeout(() => {
        console.log("第四个回调");
      }, 1000);
    }, 1000);
  }, 1000);
}, 1000);

// 解决方案:使用 Promise 或 async/await
function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function sequentialAsync() {
  console.log("开始");
  await delay(1000);
  console.log("第一个回调");
  await delay(1000);
  console.log("第二个回调");
  await delay(1000);
  console.log("第三个回调");
  await delay(1000);
  console.log("第四个回调");
}

sequentialAsync();

2. 内存泄漏

如果回调函数中引用了外部变量,而这些变量又没有被正确释放,可能会导致内存泄漏。

javascript
// 示例:潜在的内存泄漏
function createEventListener() {
  const largeObject = new Array(1000000).fill("data");

  document.getElementById("button").addEventListener("click", () => {
    console.log("按钮被点击");
    // 回调函数引用了 largeObject,可能导致内存泄漏
  });
}

createEventListener();

// 解决方案:使用 WeakRef 或手动移除事件监听器
function createEventListener() {
  const largeObject = new Array(1000000).fill("data");
  const button = document.getElementById("button");

  function handleClick() {
    console.log("按钮被点击");
  }

  button.addEventListener("click", handleClick);

  // 手动移除事件监听器
  return function cleanup() {
    button.removeEventListener("click", handleClick);
  };
}

const cleanup = createEventListener();
// 当不再需要时调用 cleanup()
// cleanup();

3. 长时间运行的任务

长时间运行的任务会阻塞主线程,导致页面卡顿。

javascript
// 示例:长时间运行的任务
function calculatePrimes(n) {
  const primes = [];
  for (let i = 2; i <= n; i++) {
    let isPrime = true;
    for (let j = 2; j < i; j++) {
      if (i % j === 0) {
        isPrime = false;
        break;
      }
    }
    if (isPrime) {
      primes.push(i);
    }
  }
  return primes;
}

console.log("开始计算");
const primes = calculatePrimes(1000000); // 这会阻塞主线程
console.log("计算完成,找到", primes.length, "个质数");

// 解决方案:使用 Web Workers 或分批处理
// 使用 Web Workers
// worker.js
/*
function calculatePrimes(n) {
  const primes = [];
  for (let i = 2; i <= n; i++) {
    let isPrime = true;
    for (let j = 2; j < i; j++) {
      if (i % j === 0) {
        isPrime = false;
        break;
      }
    }
    if (isPrime) {
      primes.push(i);
    }
  }
  self.postMessage({ primes });
}

self.onmessage = function(e) {
  const n = e.data;
  calculatePrimes(n);
};
*/

// 主线程
/*
const worker = new Worker('worker.js');

console.log('开始计算');
worker.postMessage(1000000);

worker.onmessage = function(e) {
  console.log('计算完成,找到', e.data.primes.length, '个质数');
};
*/

// 分批处理
function calculatePrimesInBatches(n, batchSize = 10000) {
  return new Promise((resolve) => {
    const primes = [];
    let current = 2;

    function processBatch() {
      const end = Math.min(current + batchSize - 1, n);
      for (let i = current; i <= end; i++) {
        let isPrime = true;
        for (let j = 2; j < i; j++) {
          if (i % j === 0) {
            isPrime = false;
            break;
          }
        }
        if (isPrime) {
          primes.push(i);
        }
      }

      current = end + 1;
      if (current <= n) {
        // 在下一个事件循环中处理下一批
        setTimeout(processBatch, 0);
      } else {
        resolve(primes);
      }
    }

    processBatch();
  });
}

async function main() {
  console.log("开始计算");
  const primes = await calculatePrimesInBatches(1000000);
  console.log("计算完成,找到", primes.length, "个质数");
}

// main();

事件循环的最佳实践

1. 使用 Promise 和 async/await

使用 Promise 和 async/await 可以使异步代码更易读、更易维护。

javascript
// 示例:使用 async/await
function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function fetchData(url) {
  const response = await fetch(url);
  const data = await response.json();
  return data;
}

async function main() {
  try {
    console.log("开始");
    await delay(1000);
    console.log("等待 1 秒");
    const data = await fetchData("https://api.example.com/data");
    console.log("获取数据:", data);
    console.log("完成");
  } catch (error) {
    console.error("错误:", error);
  }
}

main();

2. 合理使用微任务和宏任务

根据任务的性质,合理选择使用微任务或宏任务。

  • 微任务:适合处理不需要等待渲染的任务,如数据处理、状态更新等
  • 宏任务:适合处理需要等待渲染或其他异步操作的任务,如定时器、网络请求等
javascript
// 示例:合理使用微任务和宏任务
function handleUserInput() {
  // 处理用户输入(同步)
  console.log("处理用户输入");

  // 使用微任务处理数据(不阻塞渲染)
  Promise.resolve().then(() => {
    console.log("处理数据");
  });

  // 使用宏任务执行耗时操作(允许渲染)
  setTimeout(() => {
    console.log("执行耗时操作");
  }, 0);

  // 使用 requestAnimationFrame 执行动画(与渲染同步)
  requestAnimationFrame(() => {
    console.log("执行动画");
  });
}

handleUserInput();

// 输出顺序:
// 处理用户输入
// 处理数据
// 执行动画
// 执行耗时操作

3. 避免阻塞主线程

避免在主线程中执行长时间运行的任务,使用 Web Workers 或分批处理。

javascript
// 示例:使用 Web Workers
// worker.js
/*
self.onmessage = function(e) {
  const { start, end } = e.data;
  const primes = [];
  
  for (let i = start; i <= end; i++) {
    let isPrime = true;
    for (let j = 2; j < i; j++) {
      if (i % j === 0) {
        isPrime = false;
        break;
      }
    }
    if (isPrime) {
      primes.push(i);
    }
  }
  
  self.postMessage(primes);
};
*/

// 主线程
/*
const worker = new Worker('worker.js');

worker.onmessage = function(e) {
  console.log('找到质数:', e.data);
};

worker.postMessage({ start: 2, end: 1000000 });
console.log('计算中...');
*/

4. 优化事件监听器

合理使用事件监听器,避免内存泄漏。

javascript
// 示例:优化事件监听器
class Component {
  constructor() {
    this.handleClick = this.handleClick.bind(this);
    this.button = document.getElementById("button");
    this.button.addEventListener("click", this.handleClick);
  }

  handleClick() {
    console.log("按钮被点击");
  }

  destroy() {
    // 移除事件监听器
    this.button.removeEventListener("click", this.handleClick);
  }
}

// 使用
const component = new Component();
// 当不再需要时调用 destroy()
// component.destroy();

5. 理解渲染时机

理解页面渲染的时机,合理安排代码的执行顺序。

javascript
// 示例:理解渲染时机
function updateUI() {
  // 1. 执行 DOM 更新
  const element = document.getElementById("counter");
  element.textContent = parseInt(element.textContent) + 1;

  // 2. 微任务:在渲染之前执行
  Promise.resolve().then(() => {
    console.log("微任务:DOM 内容:", element.textContent);
  });

  // 3. requestAnimationFrame:在渲染之前执行
  requestAnimationFrame(() => {
    console.log("requestAnimationFrame:DOM 内容:", element.textContent);
  });

  // 4. 宏任务:在渲染之后执行
  setTimeout(() => {
    console.log("宏任务:DOM 内容:", element.textContent);
  }, 0);
}

// 初始值
document.getElementById("counter").textContent = "0";

// 调用 updateUI
updateUI();

// 输出顺序:
// 微任务:DOM 内容: 1
// requestAnimationFrame:DOM 内容: 1
// 宏任务:DOM 内容: 1

// 执行流程:
// 1. 执行同步代码(DOM 更新)
// 2. 执行微任务
// 3. 执行 requestAnimationFrame 回调
// 4. 渲染页面
// 5. 执行宏任务

事件循环的兼容性

1. 浏览器兼容性

事件循环是 JavaScript 的核心特性,所有现代浏览器都支持。在旧版本浏览器中,可能存在一些差异:

  • IE10 及以下版本的事件循环实现与现代浏览器有所不同
  • 旧版本浏览器对 Promise 和 async/await 的支持有限

2. Node.js 兼容性

Node.js 的事件循环在不同版本中也有一些变化:

  • Node.js 10+ 的事件循环行为与浏览器更接近
  • 早期版本的 Node.js 可能存在一些差异

3. 兼容性解决方案

对于不支持现代特性的环境,可以使用 polyfill 或转译工具。

javascript
// 示例:使用 polyfill
// 对于 Promise
if (!window.Promise) {
  // 使用 Promise polyfill
  // https://github.com/taylorhakes/promise-polyfill
}

// 对于 async/await
// 使用 Babel 转译
// https://babeljs.io/

总结

事件循环是 JavaScript 中处理异步操作的核心机制,它使得单线程的 JavaScript 能够高效地处理并发任务。事件循环的主要组成部分包括:

  1. 调用栈:执行同步代码和处理函数调用
  2. Web API/Node.js API:处理异步任务
  3. 消息队列:存储宏任务的回调函数
  4. 微任务队列:存储微任务的回调函数

事件循环的执行流程如下:

  1. 执行调用栈中的同步任务
  2. 执行所有微任务
  3. 渲染页面(浏览器)
  4. 从消息队列中取出一个宏任务执行
  5. 重复上述步骤

宏任务和微任务的区别:

  • 宏任务:包括 setTimeout、setInterval、DOM 事件等,需要经过事件循环才能执行
  • 微任务:包括 Promise 回调、async/await 等,在当前任务执行完毕后立即执行

通过理解事件循环的工作原理,我们可以:

  • 更好地理解异步代码的执行顺序
  • 优化页面的渲染性能
  • 避免回调地狱和内存泄漏
  • 编写更高效、更可维护的异步代码

在现代 JavaScript 中,Promise 和 async/await 已经成为处理异步操作的主流方式,但事件循环仍然是理解 JavaScript 异步行为的基础。通过掌握事件循环,我们可以更深入地理解 JavaScript 的运行机制,编写更加健壮的代码。