基础概念题
1. 请解释什么是宏任务和微任务?它们有什么区别?
答案: 宏任务和微任务都是JavaScript异步任务的分类:
- 宏任务(Macro Task):构成事件循环的基本单位,包括script整体代码、setTimeout、setInterval、I/O操作、UI渲染等
- 微任务(Micro Task):在当前宏任务结束后立即执行的任务,包括Promise回调、MutationObserver等
主要区别:
- 执行时机:微任务在当前宏任务结束后立即执行,而宏任务要等到下一轮事件循环
- 优先级:微任务优先级高于宏任务
- 队列管理:每执行一个宏任务后,会清空整个微任务队列
- 任务类型:常见的宏任务和微任务类型不同
2. 描述Event Loop的基本工作原理
答案: Event Loop是JavaScript处理异步任务的机制,基本流程:
- 执行当前宏任务(如script代码)
- 执行过程中遇到的同步代码立即执行,异步任务根据类型分发:
- 宏任务:加入宏任务队列
- 微任务:加入微任务队列
- 当前宏任务执行完毕后:
- 执行所有微任务(直到微任务队列为空)
- 微任务执行过程中产生的新微任务也会立即执行
- (浏览器)可能进行UI渲染
- 从宏任务队列取出下一个任务开始执行,开启新的事件循环
代码执行顺序题
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
解析:
- 同步代码顺序执行(script start/end)
- setTimeout回调进入宏任务队列
- Promise.then回调进入微任务队列
- 当前宏任务执行完后立即执行所有微任务
- 最后执行下一个宏任务(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、7
- 微任务队列:第一个Promise输出3并添加新的宏任务4,第二个Promise输出5
- 宏任务队列按添加顺序执行:2、6
- 最后执行微任务中添加的宏任务4
进阶理解题
5. 在Node.js中,process.nextTick和setImmediate有什么区别?
答案:
process.nextTick和setImmediate都是Node.js特有的异步API,关键区别:
-
执行时机:
process.nextTick:在当前阶段立即执行,优先级最高setImmediate:在事件循环的check阶段执行
-
执行顺序:
process.nextTick(() => console.log('nextTick')); setImmediate(() => console.log('setImmediate'));总是先输出nextTick,再输出setImmediate
-
设计目的:
nextTick:用于紧急的、需要立即执行的任务setImmediate:用于可以稍后执行的任务
-
递归风险:
- 递归调用
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更新主要有以下原因:
- 批处理更新:在同一事件循环中收集所有变更,最后统一更新,避免频繁重排/重绘
- 优先级保证:确保DOM更新在用户代码执行后、浏览器渲染前完成
- 一致性:保证在任何位置修改数据后,视图都能同步更新
- 性能优化:减少不必要的中间状态渲染
例如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、4(Promise构造函数是同步的)、9
- 微任务队列:5(Promise.then)、7(queueMicrotask)
- 宏任务队列:第一个setTimeout(2)、第二个setTimeout(8)
- 执行微任务:
- 输出5,并添加新的宏任务6
- 输出7
- 执行第一个宏任务:
- 输出2
- 添加微任务3并立即执行
- 执行第二个宏任务:输出8
- 执行微任务中添加的宏任务:输出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