大家好,我是第八哥,作为一名有10年经验的前端开发者,我发现很多同学在面试或者写异步代码时,常常被JavaScript事件循环(Event Loop) 搞得一头雾水。本文将用简单易懂的方式,带你彻底掌握事件循环的原理、执行顺序、常见陷阱和实战技巧。
一、为什么要了解事件循环?
JavaScript是单线程的,这意味着同一时间只能处理一个任务。但Web应用越来越复杂,如果同步执行,页面很容易卡死。为了解决这个问题,JS引入了事件循环机制来管理异步任务。
只要你写过setTimeout
、Promise
、async/await
,甚至操作DOM
,你就已经在用事件循环了。
二、事件循环核心概念
事件循环主要处理两类任务:宏任务(Macro Task)和微任务(Micro Task)。
1. 宏任务(Macro Task)
包括:setTimeout
、setInterval
、setImmediate
、I/O
、UI渲染等。
2. 微任务(Micro Task)
包括:Promise.then
、MutationObserver
、queueMicrotask
等。
3. 执行顺序
核心规则:每次宏任务执行完毕后,立即清空所有微任务,然后再执行下一个宏任务。
三、经典示例
console.log('start');
setTimeout(() => console.log('setTimeout'), 0);
Promise.resolve()
.then(() => console.log('promise1'))
.then(() => console.log('promise2'));
console.log('end');
输出结果:
start
end
promise1
promise2
setTimeout
原因:
- 1. 同步任务先执行
start
和end
。 - 2. 遇到
Promise
,回调放入微任务队列。 - 3. 遇到
setTimeout
,回调放入宏任务队列。 - 4. 同步执行结束后,立即执行所有微任务。
- 5. 最后进入下一个宏任务,执行
setTimeout
。
四、async/await与事件循环
async function test() {
console.log('1');
await Promise.resolve();
console.log('2');
}
test();
console.log('3');
输出结果:
1
3
2
原理:await
会让后续代码放入微任务队列,因此console.log('3')
会先执行。
五、Node.js中的事件循环
在Node.js中,事件循环稍微复杂一些,分为以下几个阶段:
- 1. timers(处理
setTimeout
/setInterval
) - 2. I/O callbacks
- 3. idle, prepare
- 4. poll(轮询I/O事件)
- 5. check(
setImmediate
回调) - 6. close callbacks
Node.js会在每个阶段结束后检查微任务队列,并立即执行所有微任务。
六、常见坑点与实战技巧
1. setTimeout vs setImmediate
在Node.js中,setImmediate
会比setTimeout(fn, 0)
更快执行,因为它在当前事件循环的check阶段触发。
2. 使用queueMicrotask
如果希望在当前宏任务结束后立刻执行某些逻辑,可以用:
queueMicrotask(() => console.log('microtask'));
3. 避免微任务过多
在Promise链中堆叠过多的微任务,可能会阻塞渲染和后续宏任务,导致页面卡顿。
七、最佳实践
- • 熟悉宏任务和微任务的区别。
- • 合理使用
Promise
和async/await
。 - • 在性能敏感场景中,避免Promise链过长。
- • 调试复杂异步逻辑时,可用
console.log
标记执行顺序。
八、总结
事件循环是JavaScript异步编程的核心。理解宏任务、微任务和执行顺序,可以帮你写出更高性能、更可控的代码。无论是浏览器还是Node.js,掌握事件循环原理都是进阶前端开发的必修课。
评论