面试题答案
一键面试JavaScript 的事件循环(Event Loop)机制
- 基本概念
- JavaScript 是单线程语言,这意味着它在同一时间只能执行一个任务。事件循环机制是 JavaScript 实现异步操作的核心机制。它的作用是不断地检查调用栈(call stack)和任务队列(task queue),当调用栈为空时,从任务队列中取出一个任务放入调用栈执行。
- 宏任务(macrotask)和微任务(microtask)
- 宏任务:常见的宏任务有
setTimeout
、setInterval
、setImmediate
(仅 Node.js 环境)、I/O
操作、UI rendering
等。宏任务会被放入宏任务队列。 - 微任务:常见的微任务有
Promise.then
、process.nextTick
(仅 Node.js 环境)、MutationObserver
等。微任务会被放入微任务队列。 - 执行顺序:
- 首先执行全局代码,这时候可能会产生宏任务和微任务。
- 当调用栈为空时,事件循环开始工作。它首先检查微任务队列,如果微任务队列中有任务,就不断从微任务队列中取出任务放入调用栈执行,直到微任务队列为空。
- 然后事件循环从宏任务队列中取出一个宏任务放入调用栈执行,执行完这个宏任务后,又会检查微任务队列,将微任务队列中的任务全部执行完,如此循环。
- 宏任务:常见的宏任务有
在实际项目中利用事件循环机制优化异步编程
- 避免阻塞调用栈
- 问题:如果在同步代码中执行大量计算,会阻塞调用栈,导致页面卡顿,用户交互不流畅。
- 解决:可以将一些耗时操作放入
setTimeout
这样的宏任务中,让其在调用栈空闲时执行。 - 代码示例:
// 模拟一个耗时操作
function heavyComputing() {
let sum = 0;
for (let i = 0; i < 1000000000; i++) {
sum += i;
}
return sum;
}
// 错误做法,阻塞调用栈
// const result1 = heavyComputing();
// console.log('result1:', result1);
// 正确做法,使用setTimeout将耗时操作放入宏任务队列
setTimeout(() => {
const result2 = heavyComputing();
console.log('result2:', result2);
}, 0);
console.log('继续执行其他代码');
在上述代码中,如果直接调用 heavyComputing
,会阻塞调用栈,后面的 console.log('继续执行其他代码');
要等 heavyComputing
执行完才会执行。而使用 setTimeout
后,heavyComputing
被放入宏任务队列,主线程继续执行后面的代码,提高了性能和用户体验。
2. 合理使用微任务
- 场景:在需要在当前任务结束后立即执行一些操作,但又不想被其他宏任务打断时,可以使用微任务。
- 代码示例:
Promise.resolve()
.then(() => {
console.log('微任务1');
return Promise.resolve();
})
.then(() => {
console.log('微任务2');
});
console.log('全局代码');
在这个例子中,Promise.resolve().then
中的回调函数会被放入微任务队列。全局代码执行完后,事件循环检查微任务队列,依次执行两个微任务回调,输出 全局代码
、微任务1
、微任务2
。这保证了微任务中的操作在当前宏任务结束后立即执行,并且不会被其他宏任务插队。