MST

星途 面试题库

面试题:JavaScript 事件循环机制与异步编程优化

深入阐述 JavaScript 的事件循环(Event Loop)机制,包括宏任务(macrotask)和微任务(microtask)的执行顺序。在实际项目中,如何利用事件循环机制优化异步编程,提高性能?请结合代码示例详细说明。
11.4万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

JavaScript 的事件循环(Event Loop)机制

  1. 基本概念
    • JavaScript 是单线程语言,这意味着它在同一时间只能执行一个任务。事件循环机制是 JavaScript 实现异步操作的核心机制。它的作用是不断地检查调用栈(call stack)和任务队列(task queue),当调用栈为空时,从任务队列中取出一个任务放入调用栈执行。
  2. 宏任务(macrotask)和微任务(microtask)
    • 宏任务:常见的宏任务有 setTimeoutsetIntervalsetImmediate(仅 Node.js 环境)、I/O 操作、UI rendering 等。宏任务会被放入宏任务队列。
    • 微任务:常见的微任务有 Promise.thenprocess.nextTick(仅 Node.js 环境)、MutationObserver 等。微任务会被放入微任务队列。
    • 执行顺序
      • 首先执行全局代码,这时候可能会产生宏任务和微任务。
      • 当调用栈为空时,事件循环开始工作。它首先检查微任务队列,如果微任务队列中有任务,就不断从微任务队列中取出任务放入调用栈执行,直到微任务队列为空。
      • 然后事件循环从宏任务队列中取出一个宏任务放入调用栈执行,执行完这个宏任务后,又会检查微任务队列,将微任务队列中的任务全部执行完,如此循环。

在实际项目中利用事件循环机制优化异步编程

  1. 避免阻塞调用栈
    • 问题:如果在同步代码中执行大量计算,会阻塞调用栈,导致页面卡顿,用户交互不流畅。
    • 解决:可以将一些耗时操作放入 setTimeout 这样的宏任务中,让其在调用栈空闲时执行。
    • 代码示例
// 模拟一个耗时操作
function heavyComputing() {
    let sum = 0;
    for (let i = 0; i < 1000000000; i++) {
        sum += i;
    }
    return sum;
}

// 错误做法,阻塞调用栈
// const result1 = heavyComputing();
// console.log('result1:', result1);

// 正确做法,使用setTimeout将耗时操作放入宏任务队列
setTimeout(() => {
    const result2 = heavyComputing();
    console.log('result2:', result2);
}, 0);
console.log('继续执行其他代码');

在上述代码中,如果直接调用 heavyComputing,会阻塞调用栈,后面的 console.log('继续执行其他代码'); 要等 heavyComputing 执行完才会执行。而使用 setTimeout 后,heavyComputing 被放入宏任务队列,主线程继续执行后面的代码,提高了性能和用户体验。 2. 合理使用微任务

  • 场景:在需要在当前任务结束后立即执行一些操作,但又不想被其他宏任务打断时,可以使用微任务。
  • 代码示例
Promise.resolve()
  .then(() => {
        console.log('微任务1');
        return Promise.resolve();
    })
  .then(() => {
        console.log('微任务2');
    });
console.log('全局代码');

在这个例子中,Promise.resolve().then 中的回调函数会被放入微任务队列。全局代码执行完后,事件循环检查微任务队列,依次执行两个微任务回调,输出 全局代码微任务1微任务2。这保证了微任务中的操作在当前宏任务结束后立即执行,并且不会被其他宏任务插队。