1、事件驱动模型:什么是事件驱动?事件循环(Event Loop)的工作原理
各位同学,咱们今天聊一个非常核心的概念——事件驱动模型。说实话,我刚开始做动画开发那会儿,对这东西也是一知半解。直到有一次,我写了一个复杂的交互动画,结果页面卡得跟幻灯片似的……嗯,从那以后,我才真正沉下心来把事件驱动搞明白。
说白了,事件驱动就是一种编程范式。它的核心思想很简单:系统不主动干活,而是等着“事情”发生,再去响应。这个“事情”就是事件——鼠标点击、键盘按下、定时器到期、网络请求返回……统统算事件。
1.1 事件驱动 vs 传统轮询
为了让你理解得更透彻,我拿一个生活中的例子来说。
假设你在等外卖:
- 轮询模式:你每隔 5 分钟给外卖小哥打个电话:“到哪了?到哪了?”——CPU 空转,浪费资源。
- 事件驱动模式:你该干嘛干嘛,等外卖小哥到了给你打电话(事件),你再下楼去拿(响应)。
你想想看,哪个更高效?显然是后者。事件驱动模型就是这种“被动响应”的哲学。
| 对比维度 | 轮询模式 | 事件驱动模式 |
|---|---|---|
| 资源占用 | 持续占用 CPU | 空闲时释放 CPU |
| 响应延迟 | 取决于轮询间隔 | 事件发生即响应 |
| 代码复杂度 | 简单但低效 | 需要回调/监听机制 |
| 适用场景 | 简单状态检查 | 高并发、UI 交互、I/O 操作 |
核心要点:事件驱动模型的核心三要素——事件源(谁产生事件)、事件监听器(谁关心事件)、事件处理器(事件来了怎么办)。
1.2 事件循环(Event Loop)的工作原理
事件驱动模型能跑起来,全靠一个幕后英雄——事件循环。我习惯把它比作一个“永不休息的调度员”。
它的工作流程其实很简单,就三步:
- 检查事件队列:看看有没有新事件排队等着处理。
- 取出事件:从队列头部拿一个事件出来。
- 执行回调:调用事件对应的处理器函数。
然后重复 1→2→3→1→2→3……直到程序退出。这就是事件循环。
我的经验:我曾经在做一个实时协作白板项目时,发现事件循环被一个耗时操作阻塞了,导致所有绘制事件都卡住。解决办法是把耗时操作拆成微任务(microtask)或者扔到 Web Worker 里。记住:事件循环最怕“长任务”。
这里有一个关键点:事件循环是单线程的。JavaScript 就是典型的例子——只有一个主线程在跑事件循环。你可能会问:“单线程怎么处理并发?”答案是:异步 + 非阻塞 I/O。
来看一段伪代码,模拟事件循环的核心逻辑:
// 事件循环的简化实现
while (true) {
// 1. 从队列中取出一个事件
const event = eventQueue.dequeue();
if (event) {
// 2. 执行事件对应的回调
const handler = eventHandlers[event.type];
if (handler) {
handler(event.data);
}
}
// 3. 处理微任务队列(Promise、MutationObserver 等)
while (microtaskQueue.length > 0) {
const microtask = microtaskQueue.dequeue();
microtask();
}
// 4. 如果队列为空,进入休眠等待新事件
if (eventQueue.isEmpty() && microtaskQueue.isEmpty()) {
waitForNewEvent();
}
}
注意:上面的代码是简化版。真实的事件循环(比如浏览器或 Node.js)还要处理宏任务(macrotask)和微任务(microtask)的优先级。微任务会在每次宏任务结束后立即执行,优先级高于下一个宏任务。
1.3 事件驱动在动画刷新中的应用
好了,理论讲完了,咱们回到动画这个主题上。动画刷新中的事件响应,说白了就是:每一帧的渲染都是由事件驱动的。
在浏览器里,requestAnimationFrame 就是一个典型的事件驱动 API。你注册一个回调函数,浏览器会在每次准备刷新屏幕时“通知”你——这就是一个事件。
// 一个简单的动画循环
function animate(timestamp) {
// 更新动画状态
updateAnimation(timestamp);
// 请求下一帧
requestAnimationFrame(animate);
}
// 启动动画
requestAnimationFrame(animate);
你想想看,如果没有事件驱动,你要怎么实现动画?只能用 setInterval 轮询,但那样你永远不知道浏览器什么时候真正刷新屏幕,很容易出现掉帧或者撕裂。
下面这张图展示了事件驱动模型在动画刷新中的完整流程:
1.4 事件驱动模型的优缺点
任何技术都有两面性,事件驱动也不例外。我在项目中踩过不少坑,这里给你总结一下:
| 优点 | 缺点 |
|---|---|
| 资源利用率高,空闲时不占 CPU | 回调地狱(Callback Hell),代码可读性差 |
| 响应及时,事件发生即处理 | 调试困难,事件流难以追踪 |
| 天然适合 UI 交互场景 | 错误处理复杂,一个未捕获的异常可能搞崩整个循环 |
| 扩展性好,可以轻松添加新的事件监听 | 内存泄漏风险——忘记移除事件监听 |
我曾经踩过的坑:有一次我做了一个长列表的滚动动画,给每个列表项都绑定了 scroll 事件监听。结果列表项销毁时忘了移除监听,导致内存泄漏,页面越来越卡。后来我养成了一个习惯:所有事件监听都要成对出现——绑定和移除。
1.5 小结
事件驱动模型,说白了就是“等事做事”。事件循环是它的心脏,不停地从队列里取事件、执行回调。在动画刷新中,requestAnimationFrame 就是最典型的事件驱动 API——浏览器告诉你“该画下一帧了”,你再去更新画面。
理解了这个模型,后面咱们聊动画帧的调度、事件优先级、防抖节流……你都会觉得顺理成章。嗯,今天就先到这儿,下节课见。