前端基础js让前端飞

使用原生 JS 实现事件委托

2017-04-15  本文已影响468人  6aaa718de24f

在介绍事件委托之前,我们先介绍另外两个知识点:其他的事件绑定方法事件冒泡及捕获

事件绑定/监听的方法

1.直接绑定

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

var ul = document.getElementById('ul')
console.log(ul)
ul.onclick = function() {
    console.log('click 事件绑定成功')
}

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

2.事件监听

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

var ul = document.getElementById('ul')
        ul.addEventListener('click', function() {
            console.log('事件绑定成功1')
        })
        ul.addEventListener('click', function() {
            console.log('事件绑定成功2')
        })

事件冒泡和捕获

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

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

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

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

当然,我们也能通过改变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('执行回调函数')
                    }
                }

总结

搜索出的解决方法有时候也存在着不足之处,并非完美,我们还是需要多实践来检验正确与否。尽信书不如无书。

上一篇下一篇

猜你喜欢

热点阅读