事件传播 vs 事件委托
Event propagation vs Event delegation
一句话:传播是浏览器的机制(捕获 → 目标 → 冒泡,见 #380);委托是我们利用这个机制的技巧—— 把监听器挂在父元素上, 通过 e.target 判断实际点了哪个子元素。
委托解决两个问题:
- 监听器数量—— 1000 行的表格挂 1000 个监听器, 内存和绑定开销都很可观;委托只要 1 个。
- 动态元素—— 后来才插进来的子元素自动就有了行为, 不用重新绑定。这一条往往更实用。
写法的关键是 e.target.closest()—— 因为用户可能点在按钮里的<span> 上, 直接比 e.target.matches() 会漏。
会追问(重点):「React 的事件是委托的吗?」——是,而且这是它的核心设计: React 把事件统一挂在根容器上 (React 17 之前挂在 document, 17 之后挂到 root 节点,这是为了支持一个页面里多个 React 版本共存), 然后用合成事件(SyntheticEvent)模拟一套跨浏览器一致的事件系统。
推论:所以在 React 里e.stopPropagation()拦得住 React 组件之间的传播, 但拦不住原生监听器—— 因为原生的已经先跑完了。 这个点答出来会很加分。
In one line: propagation is the browser’s mechanism (capture → target → bubble, see #380); delegation is the trick we play with it — put the listener on the parent and use e.target to work out which child was really clicked.
Delegation solves two problems:
- The number of listeners — a 1000-row table with 1000 listeners costs real memory and real binding time; delegation needs one.
- Dynamic elements — children inserted later already have the behaviour, with nothing to rebind. In practice this is often the bigger win.
The key to writing it is e.target.closest() — the user may have clicked a <span> inside the button, so a bare e.target.matches() misses it.
Follow-up, and this is the one that matters: “Are React events delegated?” — yes, and it is central to the design: React attaches events to the root container (to document before React 17, to the root node from 17 onwards, so that several React versions can coexist on one page), then wraps them in a SyntheticEvent to present one event system that behaves the same across browsers.
The consequence: inside React, e.stopPropagation() does stop propagation between React components, but it cannot stop a native listener — the native one already ran. Landing this point scores well.