DOM监听事件详解

作者: _刘小c | 来源:发表于2017-04-20 20:45 被阅读0次

    DOM监听事件详解

    事件定义

    ​ 事件并不是代码世界里的专用词,它仅仅是由简单的:监听、变化、通知 三要素组成

    ​ 在前端世界中,事件可以定义为:代码监听(用户),(用户)操作产生变化、(程序员)得到通知。

    DOM事件

    如何监听事件

    DOM level 0 事件监听方法: button.onclick = function(){}

    这种方法是DOM level0就支持一种方法。可以用作简单的监听。这个方法存在一个很大的问题。那就是如果一个元素绑定事件时,有可能覆盖掉前面已经绑定好的事件。

    DOM level 2 事件监听方法: button.addEventListener('click', function(){})

    这种方法和level 0的绑定方法是一致 ,但监听事件每次都会生产一个全新的匿名函数,和前面的函数完全不同,不会覆盖掉前面已经绑定好的时间。

    事件类型

    Google: DOM events MDN

    事件机制:冒泡 & 捕获

    监听事件中。子元素被点击,意味着父元素也被点击了。如果同时监听子元素和父元素,就会有个通知的先后顺序

    冒泡阶段(默认使用)

    事件从事件目标(target)开始,往上冒泡直到页面的最上一级标签。

    简单来说:就是child 先通知,parent后通知

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

    捕获阶段

    相反的,事件从最上一级标签开始往下查找,直到捕获到事件目标(target)。

    简单来说:就是parent 先通知,child 后通知


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

    事件传入属性

    事件传入属性:event,它传入了事件触发的属性

    
    event.addEventListener('eventType',function(e){
    
        console.log(e)
    }
    

    如何阻止事件:

    e.prevenDefault(): 阻止默认事件,可以在容器上阻止但不推荐

    e.stopPropagation(): 阻止冒泡事件

    使用原生 JS 实现事件委托

    基础:一个元素的父元素绑定了监听事件,而本身没有绑定监听事件。如果该元素被点击,也会触发父元素的监听事件

    让我们举个例子:

    这是一个ul,里面有4个li

        <ul id="ul">
            <li id="li1">1</li>
            <li id="li2">2</li>
            <li id="li3">3</li>
            <li id="li4">4</li>
        </ul>
    

    现在要给每个li绑定一个监听事件

    li1.addEventListener('click', function() {})
    li2.addEventListener('click', function() {})
    li3.addEventListener('click', function() {})
    li4.addEventListener('click', function() {})
    

    如果执行的函数都一样,且li个数很多,这就显得非常麻烦了。尤其是li个数不确定的时候,此时我们新增一个li,很显然,新增的li并没有绑定监听事件。

    addButton.onclick = function(){
      var li = document.createElement('li')
      li.textContent = 'new'  
      document.querySelector('ul').appendChild(li)
    }
    //新增了li,但没有自动绑定监听事件
    

    但是,在此例子中,如果点击了li,这个时候不也等于点击了ul吗?(参考上述基础)

    因此可以直接把点击事件绑定在ul上

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

    那么是否就可以简单的监听父元素从而达到监听子元素的目的呢?

    这是不对的。如果给ul添加个padding。

    可以看出,当点击padding部分,也是会触发事件的。原因是我们监听的是ul。

    所以这种绑定法,是有bug的,这明显不是我们想要的结果,于是我们可以给事件添加一个判断:

    判断一下点击的目标,如果点的是li就触发,不然就不触发。

            ul.addEventListener('click', function(e) {
                        // 检查事件源e.targe是否为Li
                        if (e.target && e.target.nodeName.toUpperCase == "LI") {
                              console.log("点击成功");
                        }
                    }
    

    ​ 这里就要科普一下用到上面所说的事件传入属性,其中:

    ​ target:触发该事件时点击的元素

    ​ currentTarget:触发该事件时监听的元素

    那么,还有什么bug吗?有的!

    尝试给li加个span试试:

        <ul id="ul">
            <li id="li1"><span>1</span></li>  //给第一个li加一个span
            <li id="li2">2</li>
            <li id="li3">3</li>
            <li id="li4">4</li>
        </ul>
    

    此时发现点击第一个li已经不触发绑定事件了。

    虽然前面说道,监听父元素就能达到监听子元素的目的。

    但是我们为了修复 ‘padding’ 的bug,添加了一个标签判断,导致 ‘span’ 不会触发绑定事件

    if (e.target && e.target.nodeName.toUpperCase == "LI") 
    

    我们必须考虑,li是否还有后代元素。这时候我们就应该先判断点击的元素的祖先元素当中有没有li,如果有li,那点击的还是li。

    所以,最后写出的事件委托函数优化如下

    ul.addEventListener('click', function() {
                        let el = e.target  \\获得实际点击的元素
                        \\以下while判定点击元素el的祖先元素是否有Li
                        while (el && !el.matches(selector)) {
                            el = el.parentNode
                            if (element === el) {
                                el = null
                            }
                        }
                        if (el) {
                            console.log('执行回调函数')
                        }
                    }
    

    相关文章

      网友评论

        本文标题:DOM监听事件详解

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