我对事件流的理解分几次补上的:先记住冒泡和捕获,后来写事件委托,最后才把浏览器派发过程串起来。
Q1: DOM 事件的历史?为什么要有事件模型?
1
1995年: 事件处理器的诞生
Netscape 2.0 引入了事件处理机制,采用捕获模型(事件从 window 向目标元素传播)。微软 IE 3.0 则采用冒泡模型(事件从目标元素向 window 传播)。两种模型完全相反,开发者苦不堪言。
2
1997年: DOM Level 1 规范
W3C 发布 DOM Level 1,但没有统一事件模型。不同浏览器的事件处理方式完全不同,开发者需要写大量兼容代码。
3
2000年: DOM Level 2 统一事件流
W3C 发布 DOM Level 2 Events 规范,统一了事件流:捕获阶段 → 目标阶段 → 冒泡阶段。同时引入了
addEventListener API,取代了 onclick 等内联事件处理器。DOM 事件流 = 捕获阶段 + 目标阶段 + 冒泡阶段,开发者可以在任意阶段处理事件。
🚫 没有统一的事件模型
一家公司有两个部门(捕获部门和冒泡部门),每个部门都有自己的审批流程。你提交一份申请,两个部门的流程完全不同——你需要分别按照两个部门的规则提交两次申请,才能完成审批。
✅ 引入统一的事件流
公司统一了审批流程:先经过捕获部门(审核资格),再经过目标部门(处理业务),最后经过冒泡部门(归档记录)。你只需要提交一次申请,流程自动走完。你还可以选择在任意阶段插入自己的审批节点(事件监听器)。
Q2: 事件流的三个阶段
事件流是事件在 DOM 树中的传播路径,分为三个阶段:事件监听器的注册方式
Q3: 事件委托(Event Delegation)
事件委托是利用事件冒泡机制,把子元素的事件处理委托给父元素。事件委托的适用场景
Q4: 事件对象(Event Object)的核心属性
事件对象包含了事件的所有信息,是事件处理的核心。事件对象的常用方法
Q5: 自定义事件(Custom Events)
除了浏览器原生事件,开发者还可以创建和派发自定义事件。Q6: 事件循环中的事件处理
事件处理和 Event Loop 密切相关——浏览器在 Event Loop 的特定阶段处理事件。Q7: 事件性能优化
事件处理不当会导致性能问题,以下是常见的优化手段。1. 防抖(Debounce)
2. 节流(Throttle)
3. 事件委托 vs 直接绑定
Q8: 事件捕获的实际应用
事件捕获在实际开发中用得较少,但在某些场景下非常有用。底层源码:浏览器是如何实现事件系统的?
浏览器的事件系统是怎么实现的?
浏览器的事件系统在 Blink 中实现,核心是事件派发器(EventDispatcher)(详见 Blink 源码:third_party/blink/renderer/core/dom/events/):
- 在捕获阶段,从 window 向目标元素传播,触发捕获阶段的事件监听器
- 在目标阶段,触发目标元素上的事件监听器(不区分捕获/冒泡)
- 在冒泡阶段,从目标元素向 window 传播,触发冒泡阶段的事件监听器
事件监听器是怎么存储和查找的?
Blink 用哈希表存储事件监听器,按事件类型分组:
十、及格回答 vs 满分回答
问题:“说说 DOM 事件流,事件委托是怎么回事?”
及格回答(60-70分):“事件流分为捕获和冒泡两个阶段。事件委托就是把子元素的事件绑定到父元素上,利用冒泡机制处理。这样可以减少事件监听器的数量,提高性能。”问题出在:知道规则但不理解细节。 满分回答(90-100分):
DOM 事件流分为三个阶段:捕获阶段(从 window 到目标元素的父级)、目标阶段(目标元素本身)、冒泡阶段(从目标元素的父级到 window)。在捕获和冒泡阶段,事件监听器按照注册顺序执行;在目标阶段,不区分捕获和冒泡,按注册顺序执行。[1]
事件委托的本质是利用冒泡机制,把子元素的事件处理委托给父元素。这样做的好处是:1)减少事件监听器数量,降低内存开销;2)动态添加的子元素自动拥有事件处理能力;3)统一管理事件,便于维护。但要注意e.target和e.currentTarget的区别——e.target是实际触发事件的元素,e.currentTarget是绑定事件监听器的元素。[2]
实际项目中,事件委托常用于列表项点击、表格行操作等场景。对于高频事件(如scroll、touchmove),需要配合防抖/节流优化。另外,passive: true选项可以告诉浏览器不会调用preventDefault,让浏览器立即开始滚动,提升页面流畅度。[3]
在 React 中,事件委托是默认行为——React 17 之前把所有事件委托到document,React 17 之后委托到根节点(#root)。这就是为什么 React 的合成事件(SyntheticEvent)和原生 DOM 事件的执行顺序有时会让人困惑。
常见追问
事件捕获和冒泡的区别?
事件捕获和冒泡的区别?
答题要点:
捕获从外到内,冒泡从内到外;目标阶段不区分。高分回答:
“事件流分为三个阶段:
- 捕获阶段:事件从 window 向目标元素传播,先触发父元素的捕获监听器,再触发子元素的
- 目标阶段:事件到达目标元素,不区分捕获/冒泡,按注册顺序执行
- 冒泡阶段:事件从目标元素向 window 传播,先触发父元素的冒泡监听器,再触发祖父元素的
addEventListener 的第三个参数:true:在捕获阶段触发false(默认):在冒泡阶段触发”
React 的合成事件和原生 DOM 事件的执行顺序?
React 的合成事件和原生 DOM 事件的执行顺序?
答题要点:
React 17 之前委托到 document,17 之后委托到根节点。高分回答:
“React 的合成事件(SyntheticEvent)是对原生 DOM 事件的封装。执行顺序取决于 React 版本:React 16(委托到 document):React 17+(委托到根节点 #root):React 17 之后,合成事件和原生事件的执行顺序更符合直觉。”
防抖和节流的区别?
防抖和节流的区别?
答题要点:
防抖是”等待”,节流是”冷却”。高分回答:
“防抖(Debounce)和节流(Throttle)都是限制函数执行频率的方法:防抖:事件触发后等待 N 毫秒,如果期间再次触发则重新计时。节流:N 毫秒内只执行一次。区别:
- 防抖:用户停止操作后才执行(延迟执行)
- 节流:用户操作期间定期执行(固定频率)”
passive 事件监听器是什么?
passive 事件监听器是什么?
答题要点:
告诉浏览器不会调用 preventDefault,可以立即滚动。高分回答:
“注意:Chrome 已经默认把
passive: true 是 addEventListener 的一个选项,用于告诉浏览器:这个事件监听器不会调用 e.preventDefault()。为什么需要这个选项?
对于 touchstart、touchmove、scroll 等高频事件,浏览器需要等待事件处理器执行完才能确定是否需要滚动。如果事件处理器耗时过长,页面会卡顿。document 级别的 touchstart、touchmove 设为 passive: true。”事件委托的优缺点?
事件委托的优缺点?
答题要点:
优点是减少监听器数量、支持动态元素;缺点是不支持所有事件。高分回答:
“事件委托的优点:
- 减少事件监听器数量:100 个子元素只需要 1 个事件监听器
- 支持动态元素:新添加的子元素自动拥有事件处理能力
- 统一管理:所有事件在一个地方处理,便于维护
- 不支持所有事件:
focus、blur、mouseenter、mouseleave等事件不冒泡 - 性能问题:如果父元素层级很深,事件需要传播很多层
- 代码复杂度:需要判断
e.target,代码可能变得复杂
如何阻止事件冒泡?
如何阻止事件冒泡?
答题要点:
stopPropagation 和 stopImmediatePropagation 的区别。高分回答:
“阻止事件传播有两种方法:区别:
stopPropagation:阻止事件传播到其他元素,但同元素上的其他监听器仍然执行stopImmediatePropagation:阻止事件传播到其他元素,同时阻止同元素上的其他监听器
preventDefault:阻止浏览器默认行为(如链接跳转),但不影响事件传播。”🔥 事件监听器的内存泄漏问题?
🔥 事件监听器的内存泄漏问题?
答题要点:
不移除事件监听器会导致内存泄漏,尤其是单页应用。高分回答:
“事件监听器如果不手动移除,会导致内存泄漏:React 中的正确做法:Vue 中的正确做法:
🔥 如何实现一个事件总线(Event Bus)?
🔥 如何实现一个事件总线(Event Bus)?
答题要点:
用发布-订阅模式实现,支持 on、off、emit。高分回答:
“事件总线的核心是发布-订阅模式:Vue 2 中的
$on、$off、$emit 就是事件总线的实现。Vue 3 移除了这个 API,推荐使用外部库(如 mitt)。“延伸阅读与引用参考
- MDN 事件参考:Mozilla 官方对所有 DOM 事件的完整参考。[1]
- JavaScript 事件委托详解:Ilya Kantor 对事件委托的深度剖析,包含性能测试数据。[2]
- Chrome 被动事件监听器:Chrome 团队对 passive 事件监听器的官方解释,解决页面卡顿问题。[3]
- DOM Level 2 Events 规范:W3C 官方规范,定义了事件流的三个阶段。[4]
