美文网首页饥人谷技术博客
捕获、冒泡与事件委托

捕获、冒泡与事件委托

作者: CoderMageFox | 来源:发表于2017-04-18 09:46 被阅读106次

学这个的时候,这东西把我搞得很头晕。于是决定写一篇Blog出来把这玩意说清楚,让和我一样在这个坑里打滚的人不那么晕菜。尽量照顾到每个细节,标准是让自己再看的时候不管忘记了多少知识点都能看得懂。

那么,开始吧。

基础知识

1.鼠标点击。

我的鼠标左键点击了网页,然后网页获取到了我的点击事件,调取了DOMAPI——真的是这样吗?

不,不是这样的。鼠标首先应该是系统级的API,所以最先知情的应该是系统。系统得知你点击了鼠标,并把这个事件传递给浏览器,然后浏览器才会通过DOMAPI通知网页。这很重要。

2.事件绑定

DOM的事件元素绑定其实没啥好说的,无非就是绑定的方法不同。一种是直接绑定,就是xx.onclick这样的。简单粗暴,DOM level0就支持。等等,什么是DOM level0?这又引出了一个隐藏知识点。DOM level0 指的是DOM level1事件之前即支持的事件,DOM level1时间基准为W3C制定第一个标准。另外一种,就是DOM level2新增的事件监听,见下文。

3.child元素和parent元素的通知问题。
根据1,那么我点击了child元素并且child元素被监听,那么就会出现一个通知顺序的问题。是父元素先通知,还是子元素先通知?那么,就顺理成章的进入了下一个介绍:

捕获和冒泡

要说捕获和冒泡,得先介绍事件监听。事件监听和绑定其实差不了太多,但是新增了一个特点,就是无论监听多少次,都不会覆盖掉前面的事件。讲白了就是事件绑定plus。而捕获和冒泡其实本质上只是Child和Parent通知的两种顺序。捕获指的是parent先通知,child后通知,而冒泡则相反.

捕获只有在早期的浏览器中才使用,第一个尝试冒泡的,居然是现在被批判为封建保守的IE。有因有果,这都是有故事的啊~

一大堆概念解释:

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

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

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

有些情况下,捕获/冒泡并不被我们所需要,比如调试。那么我们可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)来阻止。e是什么?DOM Event。当然,我们也能通过改变addEventListener的第三个参数改变事件的执行顺序。(false为冒泡阶段执行,true为捕获阶段执行,默认为false)我想,大概永远都用不到True了吧....

事件委托(事件代理)

介绍完上面的,事件委托是时候登场了。事件委托简单说起来就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
网上我找了很多篇博客,基本都是用这个例子,我就直接抄过来了:
有三个同事预计会在周一收到快递。为签收快递,有两种办法:一是三个人在公司门口等快递;二是委托给前台MM代为签收。现实当中,我们大都采用委托的方案,前台MM收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台MM也会在收到寄给新员工的快递后核实并代为签收。

嗯,我大概明白是啥意思了。来个例子加深一下印象吧。

<ul id="ul1">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>

window.onload = function(){
    var oUl = document.getElementById("ul1");
    var aLi = oUl.getElementsByTagName('li');
    for(var i=0;i<aLi.length;i++){
        aLi[i].onclick = function(){
            alert(123);
        }
    }
}

这是一段传统的的DOM操作遍历li实现事件操作的代码。有用,但是很蠢。遍历每一个li?真的有这个必要吗?
聪明的人已经想到了,那么我监听ul不就好了吗!bingo,那么用事件委托监听ul会怎么样呢?

window.onload = function(){
    var oUl = document.getElementById("ul1");
   oUl.onclick = function(){
        alert(123);
    }       

嗯,点击ul确实已经实现了功能。但是出现了一个bug.当我们点击li之外,ul之内,事件一样触发了。因为事件是绑定在ul上的啊!也许通过控制ul的样式能够解决这个问题,但是并不是一个好办法。嗯,其实写个判断就能够解决了。这里有个需要解释的e.什么是e?DOM Event.它提供了一个属性叫target,可以返回事件的目标节点。有个小坑:webkit等浏览器一般是使用e.target,而IE浏览器要用event.srcElement.而且这个target只是获取节点位置,我们还需要用nodeName获取标签名,并转化为小写做比较。

        


ul.addEventListener('click', function(e) {
                    // 检查事件源e.targe是否为Li
                    if (e.target && e.target.nodeName.toUpperCase == "LI") {

                        // 真正的处理过程在这里
                        console.log("123");
                    }
                }

这样就只有点击li会触发事件了,并且只执行一次dom操作。绝大多数的blog也是这么写的。

但是,它有bug!

如果第一个li外面套了一个span呢?

<ul id="ul1">
    <span><li>111</li></span>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>

再次点击第一个li,它居然不触发!为什么?console大法一下,发现我们点击的居然是span.那么这个循坏就错的彻彻底底了。但是其实做个小小的修改就行了。

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('123')
                    }
                }       


OK,写写抄抄终于总结完了,自己算是弄懂了,下次有机会就用起来试试~

相关文章

网友评论

    本文标题:捕获、冒泡与事件委托

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