Event Loop 到底是什么?用餐厅来理解
想象你去一家餐厅吃饭。你坐下来点餐,服务员把单子交给厨房。**厨房开始做菜需要时间,但你不会傻坐着干等。**你会先喝口水、看看手机,或者跟朋友聊天。等菜做好了,服务员会通知你。
JavaScript 的 Event Loop 就是这个「不傻等」的机制。
在浏览器里,JavaScript 是单线程的——也就是说,它一次只能做一件事。如果有个操作很慢(比如发网络请求),整个页面就会卡住。Event Loop 的出现,就是为了解决这个问题:让程序能在等待慢操作的同时,继续做其他事。
为什么要懂 Event Loop?
你可能写过这样的代码:
console.log(1);
setTimeout(() => console.log(2), 0);
console.log(3);
你以为是 1-2-3?实际输出是 1-3-2。
这就是 Event Loop 在起作用。不理解它,你会被各种"奇怪"的行为搞懵:
- 为什么 Promise 比 setTimeout 优先执行?
- 为什么页面会在大量异步操作时卡顿?
- 为什么有些代码"看起来没问题"却总是出问题?
搞懂 Event Loop,你就拿到了 JavaScript 异步编程的"说明书"。
Event Loop 的工作流程
Event Loop 的工作可以用四个关键词概括:调用栈、任务队列、微任务队列、循环。
第一步:调用栈(Call Stack)
调用栈就像一个待办清单。代码从上到下执行,每遇到一个函数就压入栈顶,执行完就弹出。如果栈满了(函数嵌套太深),浏览器会报"栈溢出"错误。
第二步:任务队列(Task Queue / Macrotask Queue)
当遇到异步操作(比如 setTimeout、文件读取),JavaScript 不会停下等待。它把这件事丢给浏览器内核去处理,自己继续执行后面的代码。异步操作完成后,结果会被放入任务队列排队。
第三步:微任务队列(Microtask Queue)
微任务和宏任务的区别,就像加急件和普通快递。Promise.then、MutationObserver 这些属于微任务,优先级更高。每次调用栈清空后,Event Loop 会先处理完所有微任务,再处理一个宏任务。
第四步:循环
Event Loop 不断重复这个过程:
- 执行调用栈里的同步代码
- 清空微任务队列
- 从任务队列取出一个宏任务执行
- 回到第 2 步
就这么循环往复,永不停歇。直到页面关闭。
一道经典面试题
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
输出是什么?
答案是:A → D → C → B
拆解一下:
- A 和 D 是同步代码,直接执行
- Promise.then 是微任务,放进微任务队列
- setTimeout 是宏任务,放进任务队列
- 同步代码执行完后,先处理微任务(C),再处理宏任务(B)
总结
Event Loop 不是某个框架的专利,它是 JavaScript 引擎内置的机制。理解它不需要你背下所有细节,但要知道三个核心:
- JavaScript 是单线程的,靠 Event Loop 实现"异步不阻塞"
- 微任务优先于宏任务,Promise 比 setTimeout 更快
- Event Loop 永不停止,只要页面开着,它就在循环
下次再遇到异步代码的执行顺序问题,别猜了。想想那个餐厅:厨房在做菜,你在旁边等着——但该你先走的,一口都不会少。