外观
事件是怎么传到监听函数的?
事件先从 window 走到目标(捕获),在目标上触发,再走回 window(冒泡)。监听默认挂在冒泡阶段。列表和表格用父节点监听,靠 event.target 认出真正被点中的元素,这就是事件委托。
原理
addEventListener(type, handler, true) 的第三个参数为 true 时,监听放在捕获阶段。传对象时用 { capture: true, once: true, passive: true }。
event.target:实际触发事件的元素。event.currentTarget:当前正在执行监听的元素,和函数里的this通常是同一个。stopPropagation():后面的捕获或冒泡不再继续。stopImmediatePropagation():同一元素上还没跑的监听也不跑。preventDefault():取消浏览器默认行为,例如链接跳转、表单提交。它不停止传播。
委托适合子节点很多、或者子节点会增减的结构:监听只绑一次,新节点不用重新绑定。不冒泡的事件不能靠父节点收到,例如 focus、blur,要用捕获,或者改听会冒泡的 focusin / focusout。
passive: true 告诉浏览器这个监听不会 preventDefault。滚动监听标成 passive,浏览器可以立刻滚动,不必等监听函数跑完。
追问
委托有什么代价?
一层父节点要自己过滤 target,复杂交互容易把判断写乱。点在子元素内部时,target 可能是按钮里的图标,要用 closest 找到真正的按钮。
React 的事件也是这样吗?
React 把合成监听委托到根容器,再用自己的事件对象分发。原生监听和 React 监听的 stopPropagation 不在同一套链上,混用时原生监听仍然能先看到事件。
易错点
preventDefault不会阻止冒泡,stopPropagation也不会取消默认行为。target和currentTarget在委托时不是同一个元素。mouseenter不冒泡,mouseover会冒泡。