MST

星途 面试题库

面试题:JavaScript异步编程与事件循环机制的深度结合

请详细描述JavaScript事件循环机制(Event Loop)在处理异步任务(包括宏任务和微任务)时的具体流程。结合一个复杂的代码示例(如下),解释每一步的执行顺序和原因。代码示例: ```javascript console.log('script start'); setTimeout(() => { console.log('setTimeout'); }, 0); Promise.resolve() .then(() => { console.log('promise1'); }) .then(() => { console.log('promise2'); }); console.log('script end'); ``` 此外,如果在上述代码中的某个`.then`回调里再添加一个`setTimeout`,会如何影响执行顺序?请分析并给出新的执行顺序。
14.8万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试
  1. JavaScript事件循环机制处理异步任务流程

    • 调用栈(Call Stack):JavaScript是单线程的,它通过调用栈来处理函数调用。当执行一个函数时,会将其添加到调用栈的栈顶,函数执行完毕后,从栈顶移除。
    • 任务队列(Task Queue):异步任务(宏任务和微任务)在其回调函数可以执行时,会被放入相应的任务队列。宏任务队列有多个,常见的宏任务如setTimeoutsetIntervalsetImmediate(Node.js环境)、I/O操作等;微任务队列只有一个,常见的微任务如Promise.thenMutationObserver等。
    • 事件循环:事件循环不断检查调用栈是否为空。当调用栈为空时,事件循环会从微任务队列中取出任务并将其放入调用栈执行,直到微任务队列为空。然后,事件循环从宏任务队列中取出一个宏任务放入调用栈执行,执行完毕后,再次检查微任务队列,重复上述过程。
  2. 给定代码执行顺序分析

    • 首先,执行console.log('script start');,输出script start
    • 接着遇到setTimeout,它是一个宏任务,其回调函数会被放入宏任务队列,等待被执行。
    • 然后Promise.resolve()返回一个已解决的Promise,其.then回调函数会被放入微任务队列。第一个.then回调() => { console.log('promise1'); }放入微任务队列,第二个.then回调() => { console.log('promise2'); }也放入微任务队列。
    • 再执行console.log('script end');,输出script end
    • 此时调用栈为空,事件循环开始处理微任务队列。从微任务队列取出第一个.then回调() => { console.log('promise1'); }放入调用栈执行,输出promise1。执行完毕后,从微任务队列取出第二个.then回调() => { console.log('promise2'); }放入调用栈执行,输出promise2。微任务队列此时为空。
    • 最后,事件循环从宏任务队列取出setTimeout的回调函数放入调用栈执行,输出setTimeout
    • 所以执行顺序为:script start -> script end -> promise1 -> promise2 -> setTimeout
  3. .then回调里添加setTimeout后的执行顺序分析: 假设在promise1.then回调里添加setTimeout,代码如下:

console.log('script start');

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

Promise.resolve()
 .then(() => {
    console.log('promise1');
    setTimeout(() => {
      console.log('new setTimeout');
    }, 0);
  })
 .then(() => {
    console.log('promise2');
  });

console.log('script end');
  • 首先,执行console.log('script start');,输出script start
  • 接着setTimeout的回调函数放入宏任务队列。
  • Promise.resolve().then回调函数放入微任务队列,第一个.then回调() => { console.log('promise1'); setTimeout(() => { console.log('new setTimeout'); }, 0); }和第二个.then回调() => { console.log('promise2'); }放入微任务队列。
  • 执行console.log('script end');,输出script end
  • 调用栈为空,处理微任务队列。取出第一个.then回调放入调用栈执行,输出promise1,此时又创建了一个新的setTimeout宏任务,其回调函数放入宏任务队列。
  • 第一个.then回调执行完毕,取出第二个.then回调放入调用栈执行,输出promise2。微任务队列此时为空。
  • 事件循环从宏任务队列取出最早放入的setTimeout(即一开始的那个)回调函数放入调用栈执行,输出setTimeout
  • 宏任务队列还有一个新的setTimeout回调,事件循环再取出该回调放入调用栈执行,输出new setTimeout
  • 新的执行顺序为:script start -> script end -> promise1 -> promise2 -> setTimeout -> new setTimeout