MST

星途 面试题库

面试题:JavaScript异步操作中的事件循环机制

详细描述JavaScript事件循环(Event Loop)的工作原理,它是如何协调异步任务(如定时器、Promise、回调函数等)与同步任务的执行顺序的?请结合代码示例说明。
37.3万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

JavaScript事件循环工作原理

  1. 调用栈(Call Stack)
    • JavaScript是单线程语言,调用栈用于存储函数调用。当一个函数被调用时,它的执行上下文会被压入调用栈。函数执行完毕后,其执行上下文会从调用栈中弹出。例如:
function add(a, b) {
    return a + b;
}
function main() {
    let result = add(2, 3);
    console.log(result);
}
main();
  • 在这个例子中,main函数被调用,其执行上下文压入调用栈,main函数内部调用add函数,add函数执行上下文压入调用栈,add函数执行完毕返回结果,其执行上下文弹出,main函数继续执行,最后main函数执行完毕,其执行上下文弹出。
  1. 任务队列(Task Queue)

    • 异步任务(如定时器、Promise等)不会立即进入调用栈执行。当异步任务有了结果(例如定时器到时间、Promise状态改变),相关的回调函数会被放入任务队列。
    • 任务队列又分为宏任务队列(Macro - Task Queue)和微任务队列(Micro - Task Queue)。常见的宏任务有setTimeoutsetIntervalsetImmediate(Node.js环境)、I/OUI渲染等;常见的微任务有Promise.thenprocess.nextTick(Node.js环境)等。
  2. 事件循环过程

    • 事件循环不断地检查调用栈是否为空。当调用栈为空时,事件循环会从微任务队列中取出任务执行,直到微任务队列为空。然后从宏任务队列中取出一个宏任务执行,执行完毕后,再次检查微任务队列并执行其中的任务,如此循环。

协调异步任务与同步任务的执行顺序

  1. 示例1:定时器与同步任务
console.log('start');
setTimeout(() => {
    console.log('setTimeout callback');
}, 0);
console.log('end');
  • 首先,console.log('start')console.log('end')是同步任务,会依次进入调用栈执行并打印。
  • setTimeout是异步任务,它的回调函数会在0毫秒(实际是在当前调用栈清空且微任务队列清空后)被放入宏任务队列。
  • 当调用栈为空时,事件循环从宏任务队列中取出setTimeout的回调函数执行,打印setTimeout callback。所以输出顺序是startendsetTimeout callback
  1. 示例2:Promise与同步任务
console.log('start');
Promise.resolve().then(() => {
    console.log('Promise then callback');
});
console.log('end');
  • console.log('start')console.log('end')是同步任务,依次进入调用栈执行并打印。
  • Promise.resolve().then的回调函数会被放入微任务队列。
  • 当调用栈为空时,事件循环先执行微任务队列中的任务,所以会打印Promise then callback。输出顺序是startendPromise then callback
  1. 示例3:结合多种异步任务
console.log('start');
setTimeout(() => {
    console.log('setTimeout callback');
    Promise.resolve().then(() => {
        console.log('Promise in setTimeout then callback');
    });
}, 0);
Promise.resolve().then(() => {
    console.log('Promise then callback');
});
console.log('end');
  • 首先console.log('start')console.log('end')同步执行并打印。
  • Promise.resolve().then的回调函数放入微任务队列,setTimeout的回调函数放入宏任务队列。
  • 调用栈为空后,先执行微任务队列中的Promise then callback
  • 微任务队列清空后,从宏任务队列中取出setTimeout的回调函数执行,打印setTimeout callback,其内部的Promise.resolve().then回调函数又放入微任务队列。
  • 再次清空微任务队列,执行Promise in setTimeout then callback。输出顺序为startendPromise then callbacksetTimeout callbackPromise in setTimeout then callback