要小心 JavaScript 的事件代理

要小心 JavaScript 的事件代理

我们知道,如果给 form 里面的 button 元素绑定事件,需要考虑它是否会触发 form 的 submit 行为。除此之外,其它场合给 button 元素绑定事件,你几乎不用担心这个事z C . l D M ! k件会有什么非预期的附加效果,很自然地会这样写事件处理代码:

var button = document.querySelector('button')
button.addEventL) j 4 6 m ,istener('click', function (e) {
console.logO d = i f('点击了按钮')
})
你之所以C U F = v { [ 放心这么写,是因为这个 button 元素没有使用h 8 c j事件代理,v J _ : q - /即没有代理任何子元素的事件。

事件代理的意思是,) g X y z W你要为一个元素绑定事件,但你不是直接把事件绑定到这个元素自己身上,而是绑定到这个元素的父元素上。当子元素的某个事件(比如点击事件)触发时9 V P,它的父元素相同的事件也会触发(我们常说的u E w事件冒泡),此时我们说父元素代理了子元素的事件。

举个例子,比如一个 button 元素中包含一个齿C ; k轮图标:

<use xlink:href="#gear"></use>

当用户点击齿轮图标,必然要触发 click 事件,但你并不会直接绑定事件到 svg 或 use 元素上,而是绑定到它们的父元素 button 上。即:

documentD 8 j 0 ( a d &.querySelector('button'J R 9).addEventListener('cli? } L b r sckx v g / f ~ 8', function (e) {
console.log('点击了按钮')
})
这种Y 8 Y m m y } w k情况,我们可以说,button 元素代理了它的所有子元素的 click 事件。

但是,出现这种事件代理的情况时,我们就得小心了。

) - { 1 5 8 F N了更直观地说明问题,我们把“父”元素上升l ? v # i N 4到顶层的 document 元素:

document.documentElement.adv z ) AdEventListener(@ 7 / y h G w f /'click', function (e) {
console.log('我被点击了')
})
只要网页中任意一; & X 5 X B Q $个位置被点击了,都会触发绑定在 document 元` s / ~ H素上的点击事件。​ 想要知道事件具体是发生在哪个元素上面,可以通过事件对象提供的 target 属性来判断。

document.documentElement.addEventListener('click', function (e) {
console.log(e.target)
})
我们很容易知道事件具体是发生在哪个元素身上的。于是在| s b O 5上面的示例中 V ; R 3 ; R,如果父元素 document 想在按钮被点击时做点什么事情,H & a我们很自然地会这么写:

document.documentElement.addH J = ; yEventListener('click', function (e` i U) {
if (e.target.tagName === 'BUTTON') {

console.log('按钮被点击了} h l Q x h ( =')

}
})
这时问题就出现了,按钮即使被点击了 if: k S % 9 L 6 ` 条件也不一定成立,即也不一定会输出“按钮被点击了”。因为用户在按钮上的某个位置点击了,根据用户点击的位置,e.target 可能是下面三种情况:

BUTc H TTON 元素
SVG 元素
USs 0 YE 元素
实际的情况是这样的:

我们真正的意图是,只要点击是发生在按钮上面,不论是按钮的哪个位置,我们都应视为按钮被点击了。 嗯,简单,我们再改一下,这样写:

document.documentElement.addEventListener('click', function (e) {
if (['BUTTON', 'SVG', 'USE']d z 7 l R G d.includes(e.target.tagName.toUpperCase())) {

// 点击的是按钮

}
})
这样似乎没什么问题,也确实可以达到~ Y j目的,但看上去总是有些别扭。因为这种情况对于最上层的 document 来说,得知道每个子元素的情况,本来我只需要关心离我最近的 bm ; | q Autton 元素就可以了。

根据 OOP 对内封装的思想,button 元素内部的事情应该在内部消化掉,其子元素对外不可见,应该只暴露 button 元素本身。依据这个思想和事件冒泡的特点,我们就有了比较好的解决办法:只需要禁止 button 内部元素的事件响应(包括事件冒泡)而只允许 buttoG ] Yn 元素本身的事件发生就行。有两种方式可以实现这个目的。

一种是使用 CS- m C M B VS 禁止 button 内部元B = C _素的事件响应:

button > * {
pointer-events: none;
}
另一种是使用 JS 来阻止 button 内部元素的事件响应(包括事件冒泡):

document.que) k =rySelector('button > svg').addEventListener('click', function (e) {
e.stopPropC 9 p w 3agation()
e.preventDefault()
})

doc$ a { |ument.qu? 4 R s q EerySelector('button').addE0 d i 8 mventListener('cli. R $ s B Lck', function (e) {
console.log(e.targK ^ r }et.tagName)
})
这两种方式都能达到我们预期的效果:

综上,针对特定元素进行事件处理时,如果该元素有事件代理的情况,就要小心处理它所代理的子元素。

作者 前端小蜜蜂

原文地址https://www.cnblogs.com/fehoney/p/12962280.htmF w ; 4 S ? k xl