今天聊聊前端开发面试中常见的一道题:“说说你对事件循环的理解。” 如果你能用简单易懂的语言和代码解释清楚,那一定能给面试官留下深刻印象。
事件循环是 JavaScript 的核心机制之一。众所周知,JavaScript 是单线程语言,意思就是同一时间只能做一件事。但单线程并不意味着它是“傻傻等着啥也干不了”的,它有一种非阻塞的机制来处理任务,这就是事件循环。
首先,我们得知道,JavaScript 中的任务分为两种:同步任务和异步任务。同步任务会直接在主线程上执行,而异步任务会被放到任务队列中,等主线程空了再处理。这种任务的执行模式,实际上就是事件循环在起作用。
为了更好地理解,我们用一个例子:
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
new Promise((resolve) => {
console.log('Promise created');
resolve();
}).then(() => {
console.log('Promise resolved');
});
console.log(3);
执行这段代码的结果是:1
、Promise created
、3
、Promise resolved
、2
。可能刚接触事件循环的同学会疑惑,为什么 Promise resolved
比 2
先输出?接下来我们就细细分析。
在 JavaScript 中,异步任务其实可以进一步细分为两类:宏任务(MacroTask) 和 微任务(MicroTask)。这两者的执行优先级不同,微任务会优先于宏任务。
宏任务包括 setTimeout
、setInterval
、UI 渲染
等。微任务包括 Promise.then
、MutationObserver
和process.nextTick
(Node.js 环境)。
JavaScript 的事件循环是这样运作的:
执行一个宏任务(包括代码一开始的同步代码)。 如果有微任务,把所有微任务依次执行完。 执行下一个宏任务。 重复以上步骤。
结合上面的代码,我们一步步剖析:
console.log(1)
是同步任务,直接执行,打印1
。setTimeout
是宏任务,它的回调被推入任务队列,等待下一轮事件循环执行。创建一个 Promise
,同步代码部分立即执行,打印Promise created
。.then
是微任务,加入微任务队列。console.log(3)
是同步任务,直接执行,打印3
。同步代码执行完毕,现在轮到微任务队列,执行 .then
的回调,打印Promise resolved
。微任务执行完,开始下一轮事件循环,执行 setTimeout
的回调,打印2
。
通过这个例子,我们可以理解为什么微任务优先于宏任务。
再举一个稍复杂的例子:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
async1();
new Promise((resolve) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
});
console.log('script end');
执行结果是:script start
、async1 start
、async2
、promise1
、script end
、async1 end
、promise2
、setTimeout
。
这段代码的执行过程是这样的:
同步代码开始执行, console.log('script start')
,输出script start
。setTimeout
是宏任务,回调放入宏任务队列。调用 async1
,console.log('async1 start')
,输出async1 start
。遇到 await
,执行async2
的同步部分,console.log('async2')
,输出async2
。await
后面的代码进入微任务队列。创建 Promise
,console.log('promise1')
,输出promise1
,then
回调进入微任务队列。console.log('script end')
,输出script end
。同步代码执行完毕,开始执行微任务队列,先执行 async1
中await
后面的代码,输出async1 end
。执行下一个微任务, then
的回调,输出promise2
。微任务执行完毕,开始执行宏任务队列中的回调,输出 setTimeout
。
这里有个重点需要特别说明:await
并不会阻塞整个线程,而是让出线程,等待异步操作完成后再恢复执行。简单来说,它本质上是语法糖,等效于 Promise
和 .then
的结合。
如果遇到“事件循环”的问题,可以按照以下逻辑清晰回答:
概念:事件循环是 JavaScript 实现单线程非阻塞的核心机制。 分类:任务分为同步任务和异步任务,异步任务分为宏任务和微任务。 执行顺序:先执行同步任务,接着执行微任务,最后执行宏任务。 代码示例:用一个简洁的例子来说明微任务优先于宏任务。 深入场景:补充 async/await
的执行机制,进一步展示对事件循环的理解。
最后,记住一点:面试官问这个问题,不是为了听你背书,而是考察你的理解和表达能力。所以,用自己的语言解释出来,清楚、简洁、有条理,就能轻松加分!
目前,对编程、职场感兴趣的同学,大家可以联系我微信:golang404,拉你进入“程序员交流群”。
虎哥私藏精品 热门推荐 虎哥作为一名老码农,整理了全网最全《前端资料合集》。