V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
mostkia
V2EX  ›  JavaScript

js 事件委托,如何对嵌套的对象进行事件绑定?

  •  
  •   mostkia · 2020-08-03 10:11:08 +08:00 · 3482 次点击
    这是一个创建于 1598 天前的主题,其中的信息可能已经有所发展或是发生改变。

    起因:客户需要匹配一个量比较大的列表,并且随时都会更新内容,点击还得绑定事件,所以理所当然的使用了 js 的事件托管,但实践中却发现了问题,以下是代码:

    html 代码

    <body>
    <ul>
        <li class="test">
            <img src="测试图片.jpg">
            <p>测试文字</p>
        </li>
        <li class="test">
            <img src="测试图片.jpg">
            <p>测试文字</p>
        </li>
        <li class="test">
            <img src="测试图片.jpg">
            <p>测试文字</p>
        </li>
    </ul>
    </body>
    

    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 ?

    22 条回复    2020-08-04 15:30:30 +08:00
    zhzbql
        1
    zhzbql  
       2020-08-03 10:32:30 +08:00   ❤️ 1
    var tisTag = event. currentTarget
    vivipure
        2
    vivipure  
       2020-08-03 10:35:35 +08:00   ❤️ 1
    首先 img 和 p 充满了 li 的空间,e.target 不可能指向 li 。忽略子元素,加个 pointer-events: none;就可以了,但是没法触发 li

    建议你获取 target 时做判断,如果是 img 和 p,就获取它的父节点 。
    iwasthere
        4
    iwasthere  
       2020-08-03 10:51:14 +08:00   ❤️ 1
    while(!tisTag. className == 'test'){
    if(tisTag. className == 'test') break;
    tisTag = tisTag.parentNode
    }
    大致写了下,应该就这个意思吧
    mostkia
        5
    mostkia  
    OP
       2020-08-03 10:53:46 +08:00
    @vivipure 好吧,看来没有更优雅的方案来做到了,可能得额外做额外的判断了,但这个方法太笨了,因为 li 内存在的东西越多,要写的判断也就越复杂,上面的代码只是示范,实际肯定还有更多嵌套之类的,看来每一种方法都不是全能的。
    otakustay
        6
    otakustay  
       2020-08-03 10:59:41 +08:00   ❤️ 1
    1 楼的 currentTarget 能解决问题的吧,或者 e.target.matches('.test *')
    sixway
        7
    sixway  
       2020-08-03 11:03:30 +08:00
    太多嵌套的需要判断。
    ChanKc
        8
    ChanKc  
       2020-08-03 11:04:17 +08:00   ❤️ 1
    #3 event.target.closest('ul > li.test')
    mostkia
        9
    mostkia  
    OP
       2020-08-03 11:09:04 +08:00
    @otakustay @iwasthere @zhzbql @ChanKc @vivipure 好的,感谢诸位出谋划策,具体我一个一个测试,已发送感谢~
    gdrk
        10
    gdrk  
       2020-08-03 11:09:18 +08:00
    1 楼的方案就可以啊
    vcfvct
        11
    vcfvct  
       2020-08-03 11:13:22 +08:00   ❤️ 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 被点击了!');
    }
    ```
    netnr
        12
    netnr  
       2020-08-03 11:19:47 +08:00   ❤️ 1
    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 被点击了!');
    }
    }
    });
    mostkia
        13
    mostkia  
    OP
       2020-08-03 11:21:34 +08:00
    @vcfvct 其实就是想一揽子让 body 监视所有页面行为,后期好维护一些,感觉页面无关内容不多时应该没什么问题,看来的确得准备额外的代码来进行判断具体点击了什么东西
    mostkia
        14
    mostkia  
    OP
       2020-08-03 11:24:04 +08:00
    @netnr @vcfvct 哈哈,好的,结果还是挺麻烦的,之前感觉事件托管是全能的,结果的确还是明确应用范围才更好一些。
    will0404
        15
    will0404  
       2020-08-03 11:27:52 +08:00
    一般来说事件绑在 ul 上比较好,用 body 的话页面上其它元素也会触发一次无意义的判断。
    otakustay
        16
    otakustay  
       2020-08-03 12:49:11 +08:00
    其实乖乖用 jQuery 就好了,自己处理这些多麻烦
    flowfire
        17
    flowfire  
       2020-08-03 17:02:23 +08:00
    你绑定在 body 上。
    事件触发在 p 或者 img 上
    对于监听器来说,只有这两个元素( target 和 currentTarget )是特殊的,其他元素都是冒泡过程中的路径而已。
    当然没有办法区别。不然他怎么知道你想区别对待 li 还是 ul ?
    建议你换一种提问方式,不要问如果在冒泡到 li 的时候触发 function,直接把你要执行的方法代码和需求贴出来,看看能不能改变方法实现这个功能?
    nonocris
        18
    nonocris  
       2020-08-03 19:21:39 +08:00
    @zhzbql @otakustay @gdrk currentTarget 不应该是始终指向绑定事件的元素吗,按楼主这个写法,currentTarget 始终都是 body 吧。是我基础不行记忆出现了偏差吗...
    otakustay
        19
    otakustay  
       2020-08-03 22:20:20 +08:00 via iPad
    @nonocris 是我记错了,currentTarget 没用
    mostkia
        20
    mostkia  
    OP
       2020-08-04 09:17:12 +08:00
    @flowfire 嗯,目前已经解决了,既然没有讨巧的办法,那就特事特办,如果出现这类内含标签的节点,直接判断是否是起泡对象本身,不是的话,则通过递归函数使用 parentNode 检索父级节点,我为需要有事件的节点都准备了一个专有属性,递归函数会直到发现这个需要的专有属性为止,这样就不会被内含的乱七八糟的嵌套影响了,为了保证递归的准确性,不使用可能发生重复的标签名称或者 class 来检索 event 对象。
    zhzbql
        21
    zhzbql  
       2020-08-04 11:29:31 +08:00
    @nonocris 你的理解没错,是我忽略了事件处理已经委托到 body 了,当成事件绑定到 li 标签处理了, 所以解决方案还是要判断事件直接触发者是否为 li 的子孙元素
    mostkia
        22
    mostkia  
    OP
       2020-08-04 15:30:30 +08:00
    @zhzbql 如果为 li 节点部署事件触发器,那本质上也没必要事件委托了,因为 li 作为子节点,是最基础的结构,它本身就是目标对象,如果托管对象也是自己,这操作我是想不通为什么要这样做,直接给 li 绑定事件不是一回事嘛😂,最少也得托管到上级,比如 ul 。不过这样的话 currentTarget 方法也没法取得目标的 li 节点了,因为它等价于 this,也就是拿到的也是 ul 本身。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5051 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 05:42 · PVG 13:42 · LAX 21:42 · JFK 00:42
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.