大家好,我是一名正在努力学习前端知识的同学。 今天这篇,咱们来把 React 事件机制 按头梳理一遍,看完直接拿去面试、写简历、嘴硬吵架都行,保证你能把「合成事件」三个字说得理直气壮。
先来点前菜:JS 原生事件机制
DOM0、DOM1、DOM2 听过没?
-
DOM0 级事件
远古写法,HTML 里写
onclick
,比如:ini<a onclick="doSomething()"></a>
缺点也明显:一个元素一个事件,一个 overwrite 掉另一个,灵活度为 0。
-
DOM1
基本没对事件机制做扩展,就是个模型过渡期,存在感很低。
-
DOM2 级事件
终于正规化了
addEventListener
:bashelement.addEventListener(type, listener, useCapture);
type
: 事件类型,比如'click'
listener
: 你的回调函数useCapture
: 布尔值,默认false
,表示在冒泡阶段执行;true
则在捕获阶段执行。
前端同学第一次用 addEventListener
,都是从 DOM2 级开始的。
事件是怎么传递的?搞清捕获和冒泡
很多人听过冒泡,但捕获是啥?其实浏览器执行顺序是:
1️⃣ 从 document
开始往目标节点走,这叫 捕获阶段
2️⃣ 到达目标节点,这里叫 目标阶段
3️⃣ 再从目标节点往上走回 document
,这叫 冒泡阶段
默认 addEventListener
是在 冒泡阶段 执行的,所以大多数事件处理是在冒泡阶段搞定的。
一句话总结:JS 事件机制是个异步大工程
JS 的回调、Promise、async/await
都是异步执行,你的事件监听器也是一样,浏览器把点击放进队列,等主线程空了再处理。
事件委托:大型项目离不开的优化利器
🥷什么是事件委托?
简单说,就是不把事件绑在每个小元素身上,而是绑在它们的公共父级(甚至直接绑在 document
)上。
然后利用 event.target
判断点的是谁,谁点谁负责。
为啥要事件委托?三个字:省资源
1. 性能最优
想象一个页面有 5000 个按钮,如果每个按钮都 addEventListener
,浏览器笑得合不拢嘴(指 CPU 飙升)。
用事件委托,只需要给外层包裹元素绑一个监听器,就能动态处理所有子元素的点击事件。
javascript
document.querySelector('#app').addEventListener('click', (e) => {
if (e.target.matches('.btn')) {
// 谁点了谁触发
}
});
2. 动态节点也不怕
滚动加载、无限下拉?一页动态插入 100 条评论?
用事件委托根本不怕!因为新节点插进去后,不需要重新注册事件,老监听器还在,event.target
自己会找到它。
3. 避免重复注册
很多时候,搞着搞着就对同一个元素绑了同一个事件多次,页面一操作结果多次触发,bug 还神秘难找。事件委托可以从源头避免这事。
再说说两个「阻止」大法
event.preventDefault()
防止默认行为,比如:
- 表单提交:
form
默认会刷新页面。 <a href="#">
点击后默认会跳转或回到顶部。
event.stopPropagation()
防止事件冒泡,有啥用?
举个栗子,做个弹窗:
- 弹窗外层
document
上有个点击监听,点哪里都关闭弹窗。 - 弹窗内部按钮也有点击事件,点按钮不能关闭弹窗。
这时就得在内部按钮点击时 stopPropagation
,否则一点击按钮,冒泡到外层就把弹窗关了。
React 的合成事件:其实是个高级事件代理
⚙️ React 怎么处理事件?
React 不会真的在每个元素上绑监听器,而是用了一套 事件代理 机制:
- 所有事件都挂在根节点
#root
上,咱们的<div>
<button>
并没有真正绑定原生事件。 - 发生事件时,React 先收集,再在内部处理。
- 这玩意叫 SyntheticEvent(合成事件) 。
事件池(Event Pooling)
老版本 React(17 以前)里为了节省内存,每次事件执行完后会把事件对象里的属性清空回收,下次复用。
所以以前咱们经常看见 e.persist()
,就是告诉 React:别清空这个事件对象,我还要用。
好消息是:React 17+ 以后,这个事件池基本安全了,大多数时候可以放心使用事件对象,不需要 persist
。
总结:前端事件三板斧
1️⃣ 理解 捕获阶段 、冒泡阶段 、目标阶段
2️⃣ 合理用好 事件委托 ,性能翻倍不是梦
3️⃣ 面对 React 的 合成事件,别慌,它就是事件代理 + 池化,React 帮你兜底了。
最后,彩蛋来了
面试官:React 的事件是怎么实现的?
你:事件委托挂在 #root
,内部用 SyntheticEvent,事件池可回收,低开销高性能。
面试官:过!
好了,这就是今天的分享,如果觉得有用,麻烦点个 收藏一下,我不做代码混子,就做码界段子手,我们下篇见!