V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
mostkia

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

  •  
  •   mostkia · Aug 3, 2020 · 4307 views
    This topic created in 2105 days ago, the information mentioned may be changed or developed.

    起因:客户需要匹配一个量比较大的列表,并且随时都会更新内容,点击还得绑定事件,所以理所当然的使用了 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 replies    2020-08-04 15:30:30 +08:00
    zhzbql
        1
    zhzbql  
       Aug 3, 2020   ❤️ 1
    var tisTag = event. currentTarget
    vivipure
        2
    vivipure  
       Aug 3, 2020   ❤️ 1
    首先 img 和 p 充满了 li 的空间,e.target 不可能指向 li 。忽略子元素,加个 pointer-events: none;就可以了,但是没法触发 li

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