【DeepSeek帮我准备前端面试100问】(课后题一)宏任务、微任务与Event Loop面试题集锦

253 阅读5分钟

基础概念题

1. 请解释什么是宏任务和微任务?它们有什么区别?

答案: 宏任务和微任务都是JavaScript异步任务的分类:

  • 宏任务(Macro Task):构成事件循环的基本单位,包括script整体代码、setTimeout、setInterval、I/O操作、UI渲染等
  • 微任务(Micro Task):在当前宏任务结束后立即执行的任务,包括Promise回调、MutationObserver等

主要区别

  1. 执行时机:微任务在当前宏任务结束后立即执行,而宏任务要等到下一轮事件循环
  2. 优先级:微任务优先级高于宏任务
  3. 队列管理:每执行一个宏任务后,会清空整个微任务队列
  4. 任务类型:常见的宏任务和微任务类型不同

2. 描述Event Loop的基本工作原理

答案: Event Loop是JavaScript处理异步任务的机制,基本流程:

  1. 执行当前宏任务(如script代码)
  2. 执行过程中遇到的同步代码立即执行,异步任务根据类型分发:
    • 宏任务:加入宏任务队列
    • 微任务:加入微任务队列
  3. 当前宏任务执行完毕后:
    • 执行所有微任务(直到微任务队列为空)
    • 微任务执行过程中产生的新微任务也会立即执行
  4. (浏览器)可能进行UI渲染
  5. 从宏任务队列取出下一个任务开始执行,开启新的事件循环

代码执行顺序题

3. 以下代码的输出顺序是什么?

console.log('script start');

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

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');

答案

script start
script end
promise1
promise2
setTimeout

解析

  1. 同步代码顺序执行(script start/end)
  2. setTimeout回调进入宏任务队列
  3. Promise.then回调进入微任务队列
  4. 当前宏任务执行完后立即执行所有微任务
  5. 最后执行下一个宏任务(setTimeout)

4. 以下代码的输出顺序是什么?

console.log('1');

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

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

Promise.resolve().then(() => console.log('5'));

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

console.log('7');

答案

1
7
3
5
2
6
4

解析

  1. 同步代码:1、7
  2. 微任务队列:第一个Promise输出3并添加新的宏任务4,第二个Promise输出5
  3. 宏任务队列按添加顺序执行:2、6
  4. 最后执行微任务中添加的宏任务4

进阶理解题

5. 在Node.js中,process.nextTick和setImmediate有什么区别?

答案process.nextTicksetImmediate都是Node.js特有的异步API,关键区别:

  1. 执行时机

    • process.nextTick:在当前阶段立即执行,优先级最高
    • setImmediate:在事件循环的check阶段执行
  2. 执行顺序

    process.nextTick(() => console.log('nextTick'));
    setImmediate(() => console.log('setImmediate'));
    

    总是先输出nextTick,再输出setImmediate

  3. 设计目的

    • nextTick:用于紧急的、需要立即执行的任务
    • setImmediate:用于可以稍后执行的任务
  4. 递归风险

    • 递归调用process.nextTick会导致I/O饥饿(阻止事件循环继续)
    • setImmediate则没有这个问题

6. 如何实现一个微任务优先的调度器?

答案: 可以利用Promise和queueMicrotask实现:

class MicrotaskScheduler {
  constructor() {
    this.queue = [];
    this.isProcessing = false;
  }

  addTask(task) {
    this.queue.push(task);
    if (!this.isProcessing) {
      this.processQueue();
    }
  }

  processQueue() {
    this.isProcessing = true;
    queueMicrotask(() => {
      const task = this.queue.shift();
      if (task) {
        try {
          task();
        this.processQueue();
        } catch (e) {
          console.error('Task error:', e);
        }
      } else {
        this.isProcessing = false;
      }
    });
  }
}

// 使用示例
const scheduler = new MicrotaskScheduler();
scheduler.addTask(() => console.log('Task 1'));
scheduler.addTask(() => console.log('Task 2'));

实际应用题

7. 如何优化大量数据处理的性能,避免阻塞主线程?

答案: 可以采用任务分片策略,结合宏任务和微任务:

function processLargeData(data, chunkSize, callback) {
  let index = 0;
  
  function processChunk() {
    const chunk = data.slice(index, index + chunkSize);
    // 处理当前分片
    for (let i = 0; i < chunk.length; i++) {
      // 数据处理逻辑
    }
    
    index += chunkSize;
    
    if (index < data.length) {
      // 使用不同的异步API进行分片控制
      if (index % (chunkSize * 10) === 0) {
        // 每处理10个分片让出一次事件循环
        setTimeout(processChunk, 0);
      } else {
        // 其他时候使用微任务保持较高优先级
        queueMicrotask(processChunk);
      }
    } else {
      callback();
    }
  }
  
  processChunk();
}

8. 为什么在Vue/React等框架中,DOM更新使用微任务?

答案: 现代框架使用微任务进行DOM更新主要有以下原因:

  1. 批处理更新:在同一事件循环中收集所有变更,最后统一更新,避免频繁重排/重绘
  2. 优先级保证:确保DOM更新在用户代码执行后、浏览器渲染前完成
  3. 一致性:保证在任何位置修改数据后,视图都能同步更新
  4. 性能优化:减少不必要的中间状态渲染

例如Vue的$nextTick实现:

// 简化版实现
let callbacks = [];
let pending = false;

function flushCallbacks() {
  pending = false;
  const copies = callbacks.slice(0);
  callbacks.length = 0;
  for (let i = 0; i < copies.length; i++) {
    copies[i]();
  }
}

function nextTick(cb) {
  callbacks.push(cb);
  if (!pending) {
    pending = true;
    queueMicrotask(flushCallbacks);
  }
}

综合难题

9. 分析以下复杂代码的执行顺序

console.log('1');

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

new Promise((resolve) => {
  console.log('4');
  resolve();
}).then(() => {
  console.log('5');
  setTimeout(() => console.log('6'), 0);
});

queueMicrotask(() => console.log('7'));

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

console.log('9');

答案

1
4
9
5
7
2
3
8
6

详细解析

  1. 同步代码:1、4(Promise构造函数是同步的)、9
  2. 微任务队列:5(Promise.then)、7(queueMicrotask)
  3. 宏任务队列:第一个setTimeout(2)、第二个setTimeout(8)
  4. 执行微任务:
    • 输出5,并添加新的宏任务6
    • 输出7
  5. 执行第一个宏任务:
    • 输出2
    • 添加微任务3并立即执行
  6. 执行第二个宏任务:输出8
  7. 执行微任务中添加的宏任务:输出6

10. 如何实现一个优先级任务调度系统?

答案: 结合宏任务和微任务实现多级优先级:

class TaskScheduler {
  constructor() {
    this.highPriorityQueue = [];  // 微任务
    this.mediumPriorityQueue = []; // requestAnimationFrame
    this.lowPriorityQueue = [];   // setTimeout
  }

  addHighPriorityTask(task) {
    this.highPriorityQueue.push(task);
    queueMicrotask(() => this.runHighPriorityTasks());
  }

  addMediumPriorityTask(task) {
    this.mediumPriorityQueue.push(task);
    requestAnimationFrame(() => this.runMediumPriorityTasks());
  }

  addLowPriorityTask(task) {
    this.lowPriorityQueue.push(task);
    setTimeout(() => this.runLowPriorityTasks(), 0);
  }

  runHighPriorityTasks() {
    while (this.highPriorityQueue.length) {
      const task = this.highPriorityQueue.shift();
      task();
    }
  }

  runMediumPriorityTasks() {
    const tasks = this.mediumPriorityQueue.splice(0);
    tasks.forEach(task => task());
  }

  runLowPriorityTasks() {
    if (this.highPriorityQueue.length || this.mediumPriorityQueue.length) {
      // 如果有更高优先级任务,重新调度
      setTimeout(() => this.runLowPriorityTasks(), 0);
    } else {
      const task = this.lowPriorityQueue.shift();
      if (task) task();
    }
  }
}

// 使用示例
const scheduler = new TaskScheduler();
scheduler.addLowPriorityTask(() => console.log('Low priority'));
scheduler.addHighPriorityTask(() => console.log('High priority'));
scheduler.addMediumPriorityTask(() => console.log('Medium priority'));
// 输出顺序:High priority -> Medium priority -> Low priority