美文网首页
事件冒泡&事件委托

事件冒泡&事件委托

作者: lyp82nkl | 来源:发表于2019-06-20 23:10 被阅读0次

    定义

    DOM结构是一个树型结构,当一个HTML元素触发一个事件时,该事件会在元素结点与根结点之间的路径传播,路径所经过的结点都会收到该事件,这个传播过程可称为 DOM 事件流(DOM event flow )。
    事件流分为三个阶段

    • 捕获阶段:事件从Document节点自上而下向目标节点传播的阶段;
    • 目标阶段:真正的目标节点正在处理事件的阶段;
    • 冒泡阶段:事件从目标节点自上而下向Document节点传播的阶段。

    事件绑定/监听的方法

    1. 直接绑定

    直接在DOM元素上绑定onclick、onmouseover、onmouseout、onmousedown、onmouseup、ondblclick、onkeydown、onkeypress、onkeyup等事件

    x.onclick = function(){
      console.log(‘1’)
    }
    x.onclick = function(){
      console.log(‘2’)
    }
    //1不会打印出来,会被后面的2给覆盖
    

    这种方法最简单,也是DOM level0最早支持的一种方法。但是这个方法存在一个很大的问题。那就是如果一个元素绑定事件时,有可能覆盖掉前面已经绑定好的事件!尤其是存在多个js文件时。为了解决这个问题,level2新增了事件监听

    2. 事件监听

    事件监听实现的功能和直接绑定差不多,但是新增了一个特点。那就是无论监听次,都不会覆盖掉前面的监听事件。本质原因是监听事件每次都会生产一个全新的匿名函数,和前面的函数完全不同,自然不会覆盖。

    x.addEventListener(‘click’,function(){
      console.log(1)
    })
    x.addEventListener(‘click’,function(){
      console.log(2)
    })
    //1和2都会被打印出来,不会被覆盖掉
    

    事件冒泡和捕获

    事件捕获:当某个元素触发某个事件(如onclick),顶层对象document就会发出一个事件流,随着DOM树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程中,事件相应的监听函数是不会被触发的。

    事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。

    事件冒泡:从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被一次触发。

    阻止冒泡

    如果想阻止事件冒泡,可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)来组织事件的冒泡传播。

    document.getElementById(‘div’).addEventListener(‘click’,function(e){
      e.stopPropagation();
    })
    

    通过改变addEventListener的第三个参数改变事件的执行顺序。(false为冒泡阶段执行,true为捕获阶段执行,默认为false)

    事件委托

    我们实现事件委托就是基于上面几个理论。

    现在,我们先设想一种情况,ul标签中的每个li标签都要绑定一个点击事件

    <ul id="ul">
            <li id="l1">1</li>
            <li id="l2">2</li>
            <li id="l3">3</li>
            <li id="l4">4</li>
        </ul>
    
    l1.addEventListener('click', function() {})
    l2.addEventListener('click', function() {})
    l3.addEventListener('click', function() {})
    l4.addEventListener('click', function() {})
    

    但如果执行的函数都一样,且li个数很多,这就显得非常麻烦了。尤其是li个数不确定的时候(JavaScript动态生成),这种方式更是不适用。

    聪明的你可能已经想到,我点击了li,这个时候不也等于点击了ul吗?那我直接把点击事件绑定在ul上不就好了?

    var ul = document.getElementById('ul')
    ul.addEventListener('click', function() {})
    

    我们点击li,确实是实现了我们想要的功能。但又有一个新的问题。那就是ul如果有padding可能就会出bug。我们点击li之外,ul之内时,事件也触发了!这种结果肯定不是我们想要的。

    那我触发事件之前先判断一下点击的目标不就好了?如果点的是li就触发,不然就不触发。

    ul.addEventListener('click', function(e) {
                        // 检查事件源e.targe是否为Li
                        if (e.target && e.target.nodeName.toUpperCase == "LI") {
    
    
                            // 真正的处理过程在这里
                            console.log("点击成功");
                        }
                    }
    

    再次测试函数,发现基本没有问题了。点击li触发事件,li之外不触发事件。这也是大多数人实现事件委托的方法,但却不是一个好的实现方法。因为这种方法也存在着明显的bug

    <ul id="ul">
            <li id="l1"><span>1</span></li>
            <li id="l2">2</li>
            <li id="l3">3</li>
            <li id="l4">4</li>
        </ul>
    

    如果第一个li外面套了一个span的情况呢?我们再次点击第一个li,它!不!触!发!事!件!我们console.log出这个时候点击的标签,发现是span,自然不触发事件。显然我们之前写的事件委托有bug。有可能我们点击li的时候,li还有后代元素。这时候我们就应该先判断点击的元素的祖先元素当中有没有li,如果有li,那点击的还是li,如果没有,那就是真的没有了。最后写出的事件委托函数如下

    ul.addEventListener('click', function() {
                        let el = e.target
                        while (el && !el.matches(selector)) {
                            el = el.parentNode
                            if (element === el) {
                                el = null
                            }
                        }
                        if (el) {
                            console.log('执行回调函数')
                        }
                    }
    

    相关文章

      网友评论

          本文标题:事件冒泡&事件委托

          本文链接:https://www.haomeiwen.com/subject/yfysqctx.html