起因:客户需要匹配一个量比较大的列表,并且随时都会更新内容,点击还得绑定事件,所以理所当然的使用了 js 的事件托管,但实践中却发现了问题,以下是代码:
html 代码
js 代码
var body = document.querySelector('body');
body.addEventListener('click',function(event){
var event = event || window.event;
var tisTag = event.srcElement || event.target;
if(tisTag.className == 'test'){
alert('li 被点击了!');
}
});
出现的问题:
点击 li 列表后,激活的对象是 p 标签和 img 标签,导致预先编写的针对 li 标签的代码没有反应。有没有办法忽略子元素 p 和 img ?
var tisTag = event. currentTarget
首先 img 和 p 充满了 li 的空间,e.target 不可能指向 li 。忽略子元素,加个 pointer-events: none;就可以了,但是没法触发 li
建议你获取 target 时做判断,如果是 img 和 p,就获取它的父节点 。
找到最近的 li 父节点
https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
https://github.com/material-components/material-components-web/blob/75fb21a852985fe34a4e8f9dc04358cda6949b06/packages/mdc-list/component.ts#L262
https://github.com/material-components/material-components-web/blob/75fb21a852985fe34a4e8f9dc04358cda6949b06/packages/mdc-dom/ponyfill.ts#L29
while(!tisTag. className == 'test'){
if(tisTag. className == 'test') break;
tisTag = tisTag.parentNode
}
大致写了下,应该就这个意思吧
好吧,看来没有更优雅的方案来做到了,可能得额外做额外的判断了,但这个方法太笨了,因为 li 内存在的东西越多,要写的判断也就越复杂,上面的代码只是示范,实际肯定还有更多嵌套之类的,看来每一种方法都不是全能的。
1 楼的 currentTarget 能解决问题的吧,或者 e.target.matches('.test *')
太多嵌套的需要判断。
event.target.closest('ul > li.test')
好的,感谢诸位出谋划策,具体我一个一个测试,已发送感谢~
1 楼的方案就可以啊
event 要 bubble up 上去 target 总是点击的那个最深 element, 所以还是应该得往上找 parent 才行吧, 不一定非得绑定在 body 上, 在 最 close 的`ul`上可能能让范围小一些.
```
var body = document.querySelector('body');
body.addEventListener('click', function (event) {
var event = event || window.event;
let tisTag = event.target;
while (tisTag && tisTag.tagName !== 'LI') {
tisTag = tisTag.parentNode
if (tisTag === body) {
tisTag = null
break;
}
}
if (tisTag && tisTag.className === 'test') {
alert('li 被点击了!');
}
```
var body = document.querySelector('body'),
lis = document.getElementsByTagName('li');
body.addEventListener('click', function (event) {
var event = event || window.event;
var tisTag = event.srcElement || event.target;
for (var i = 0; i < lis.length; i++) {
var li = lis[i];
if (li.contains(tisTag)) {
alert('li 被点击了!');
}
}
});
其实就是想一揽子让 body 监视所有页面行为,后期好维护一些,感觉页面无关内容不多时应该没什么问题,看来的确得准备额外的代码来进行判断具体点击了什么东西
哈哈,好的,结果还是挺麻烦的,之前感觉事件托管是全能的,结果的确还是明确应用范围才更好一些。
一般来说事件绑在 ul 上比较好,用 body 的话页面上其它元素也会触发一次无意义的判断。
其实乖乖用 jQuery 就好了,自己处理这些多麻烦
你绑定在 body 上。
事件触发在 p 或者 img 上
对于监听器来说,只有这两个元素( target 和 currentTarget )是特殊的,其他元素都是冒泡过程中的路径而已。
当然没有办法区别。不然他怎么知道你想区别对待 li 还是 ul ?
建议你换一种提问方式,不要问如果在冒泡到 li 的时候触发 function,直接把你要执行的方法代码和需求贴出来,看看能不能改变方法实现这个功能?
currentTarget 不应该是始终指向绑定事件的元素吗,按楼主这个写法,currentTarget 始终都是 body 吧。是我基础不行记忆出现了偏差吗...
是我记错了,currentTarget 没用
嗯,目前已经解决了,既然没有讨巧的办法,那就特事特办,如果出现这类内含标签的节点,直接判断是否是起泡对象本身,不是的话,则通过递归函数使用 parentNode 检索父级节点,我为需要有事件的节点都准备了一个专有属性,递归函数会直到发现这个需要的专有属性为止,这样就不会被内含的乱七八糟的嵌套影响了,为了保证递归的准确性,不使用可能发生重复的标签名称或者 class 来检索 event 对象。
你的理解没错,是我忽略了事件处理已经委托到 body 了,当成事件绑定到 li 标签处理了, 所以解决方案还是要判断事件直接触发者是否为 li 的子孙元素
如果为 li 节点部署事件触发器,那本质上也没必要事件委托了,因为 li 作为子节点,是最基础的结构,它本身就是目标对象,如果托管对象也是自己,这操作我是想不通为什么要这样做,直接给 li 绑定事件不是一回事嘛