MST
星途 面试题库

面试题:JavaScript异步编程中微任务和宏任务与回调函数的关系

深入分析JavaScript事件循环机制下,微任务队列和宏任务队列与回调函数之间的关系。说明在不同场景下,回调函数是如何被分配到相应队列中执行的,同时结合Promise、setTimeout等具体例子进行详细阐述。
45.0万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

1. 事件循环机制基础

JavaScript是单线程语言,这意味着它在同一时间只能执行一个任务。事件循环(Event Loop)机制的存在,使得JavaScript可以在非阻塞的情况下处理多个任务。事件循环的核心思想是:主线程从“任务队列”中读取任务,执行任务,然后再去读取下一个任务,如此循环。

2. 宏任务队列和微任务队列

  • 宏任务(Macrotask):也称为Task。常见的宏任务有setTimeoutsetIntervalsetImmediate(仅在Node.js环境)、I/O操作、UI rendering等。宏任务队列可以有多个,但在事件循环中,每次事件循环只会处理一个宏任务队列中的一个任务。
  • 微任务(Microtask):常见的微任务有Promise.thenprocess.nextTick(仅在Node.js环境)、MutationObserver等。微任务队列只有一个,在当前宏任务执行完后,事件循环会立即处理微任务队列中的所有任务,直到微任务队列为空,然后才会去处理下一个宏任务。

3. 回调函数与任务队列关系

  • 宏任务回调:像setTimeout的回调函数会被放入宏任务队列。当setTimeout设定的延迟时间到达后,其回调函数就会被推入宏任务队列,等待主线程执行完当前宏任务且微任务队列清空后执行。例如:
setTimeout(() => {
    console.log('setTimeout callback');
}, 0);
console.log('main script');

这里,console.log('main script')先执行,因为它在主线程中按顺序执行。而setTimeout的回调函数被放入宏任务队列,等主线程任务执行完,且微任务队列清空后才执行,所以最后输出setTimeout callback

  • 微任务回调Promise.then的回调函数属于微任务回调。当Promise状态改变后,then方法中的回调函数会被放入微任务队列。例如:
Promise.resolve().then(() => {
    console.log('Promise then callback');
});
console.log('main script');

在这个例子中,console.log('main script')先执行,然后Promise.then的回调函数被放入微任务队列,在主线程任务执行完后,事件循环立即处理微任务队列,所以接着输出Promise then callback

4. 综合例子分析

setTimeout(() => {
    console.log('setTimeout 1');
    Promise.resolve().then(() => {
        console.log('Promise then in setTimeout 1');
    });
}, 0);

Promise.resolve().then(() => {
    console.log('Promise then 1');
    setTimeout(() => {
        console.log('setTimeout in Promise then 1');
    }, 0);
});

setTimeout(() => {
    console.log('setTimeout 2');
}, 0);
  1. 首先执行主线程代码,遇到第一个setTimeout,其回调函数被放入宏任务队列。
  2. 遇到第一个Promise.then,其回调函数被放入微任务队列。
  3. 遇到第二个setTimeout,其回调函数被放入宏任务队列。
  4. 主线程代码执行完毕,开始处理微任务队列,输出Promise then 1,接着在这个微任务回调中又遇到setTimeout,其回调函数被放入宏任务队列。
  5. 微任务队列处理完毕,开始处理宏任务队列,先执行第一个setTimeout回调,输出setTimeout 1,然后在这个回调中的Promise.then回调被放入微任务队列。
  6. 宏任务队列中该任务执行完毕,再次处理微任务队列,输出Promise then in setTimeout 1
  7. 微任务队列处理完毕,继续处理宏任务队列,执行第二个setTimeout回调,输出setTimeout 2
  8. 宏任务队列处理完毕,再次处理微任务队列(此时为空)。
  9. 宏任务队列处理完毕,执行setTimeout in Promise then 1回调。

综上,在JavaScript事件循环机制下,回调函数根据其所属的任务类型(宏任务或微任务)被分配到相应队列,按照事件循环规则依次执行。