JS事件中特性中有两个需要注意的,分别是JS事件的冒泡和委派,委派又是基于冒泡的,所以本博文聊一下这两个特性。

然后再基于这个特性分析一下事件处理、事件委派的几个方法on()bind()live()delegate()之间的区别,总结在文末。

文中代码均使用jquery实现。

1. 事件冒泡

1.1 概念

指的是某一个子元素的某个事件被触发了,会自动触发其所有父元素(包括祖先元素)的相同类型的事件。

当然,如果父元素没有相同类型的事件,页面上则不会有任何不同的效果。

Eg:我们在平时的开发过程中,经常遇到在一个div(这个div可以是其他元素)包裹另一个div的情况。如果这两个div都绑定了相同类型的事件。当我们触发子元素的事件后,父元素及所有祖先元素的相同事件都会被触发。

通俗的说,你再自己的卧室看电视,电视的声音你可以听到,在客厅聊天的父母也能听到;如果整个屋子隔音不好,和你住同一栋楼同一层的隔壁老王也能听到。

1.2 演示代码及效果

代码演示如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>时间冒泡和委派</title>
</head>
<body>
    <script src="js/jquery-3.3.1.min.js"></script>
    <div id="div1">
        <div id="div2">
            <div id="div3">div3</div>
            div2
        </div>
        div1
    </div>
    <style>
        #div1 {
            width: 300px;
            height: 300px;
            background-color: lightpink;
        }

        #div2 {
            width: 200px;
            height: 200px;
            background-color: lightgreen;
        }

        #div3 {
            width: 100px;
            height: 100px;
            background-color: yellowgreen;
        }
    </style>
     <script type="text/javascript">

         $("#div1").click(function () {
             alert("我是div1");
         });
         $("#div2").click(function () {
             alert("我是div2");
         });
         $("#div3").click(function () {
             alert("我是div3");
         });
     </script>
</body>
</html>

效果如下:

01.js-event-bubble.gif

1.3 问题解决方法

我们一般只希望点击内部的div之后,只由内部div响应该事件,不希望外层的div的相同事件也执行,这时候我们就要用到阻止冒泡。

换句话来说,你可以可躲在被窝里,或者墙壁的隔音效果很好,也就阻止了声音被客厅的服务听到。

阻隔声音可以理解为阻止事件冒泡。

可以通过元素的事件状态对象Event的方法阻止事件冒泡:stopPropagation。代码如下:

$("#div3").click(function (event) {
     event = event || window.event;  // 确保获取event对象
     alert("我是div3");
     event.stopPropagation();   //阻止冒泡,父、祖先元素的相同事件则不会被触发
 });

接下来分析一下,使用标准事件可能存在的缺点。

2. 标准事件的缺点

就是使用传统方式为每个子元素添加相同的事件的缺点。

示例代码

<ul id="list">
    <li>1111<li>
    <li>2222<li>
    <li>3333<li>
    <li>4444<li>
</ul>

这是一个ul,里面包含4个li,如果想要给li加上鼠标事件改变背景颜色,我们一般这样写:

<script type="text/javascript">
    // 鼠标悬停高亮变红
    $("#list li").mouseover(function () {
        $(this).css("background", "red");
    })
    // 鼠标移开,恢复原色
    $("#list li").mouseout(function () {
        $(this).css("background", "white");
    })
</script>

这样当我们悬停在list里面的任何一个li都会高亮,但这样做有两个弊端:

  1. 浪费资源。因为这种方式是给每个li都加上了事件,li的个数少的话还没事,如果li多的话会很耗费资源。

  2. 后期添加的动态资源没有绑定该事件。因为浏览器会按照书写顺序执行HTML中代码,动态添加li元素时,为li绑定事件的代码不会重新运行一遍。既:尽管这个新的元素也能够匹配选择器"#list li",但是由于这个元素是在调用mouseovermouseout之后添加的,所以悬停移出这个元素不会有任何效果。

弊端2的演示代码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>事件委派</title>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>
    <body>
        <ul id="list">
            <li>1111</li>
            <li>2222</li>
            <li>3333</li>
            <li>4444</li>
        </ul>
        <script type="text/javascript">
            // 标准事件写法
            // 鼠标悬停高亮变红
            $("#list li").mouseover(function () {
                $(this).css("background", "red");
            });
            // 鼠标移开,恢复原色
            $("#list li").mouseout(function () {
                $(this).css("background", "white");
            });

            $("#list").append("<li>555</li>\n" +
                "    <li>666</li>");
        </script>
    </body>
</html>

值为555666的两个列表项没有对应的事件。效果如下:

02.js-standard-event-bug.gif

诚然,这种情况,可以通过先添加元素,然后再绑定事件解决;但是在生产环境中,你永远不清楚后面会再填加什么内容。

所以,针对以上问题,可以用事件委派来一并解决。

3. 事件委派

3.1 概念

事件委派的定义就是,根据事件冒泡的特性,把原来绑定在子元素身上的事件绑定在父元素身上,就是把事件委派给父元素。

这样就会减少事件的绑定次数,从而提高性能。也可以为后期动态添加的子元素“绑定”事件。

3.2 相关API

jQuery1.7开始,jQuery引入了全新的事件绑定机制,on()off()两个函数统一处理事件绑定。

而在此之前有bind(),live(), delegate()等方法来处理事件绑定,jQuery从性能优化以及方式统一方面考虑决定推出新的函数来统一事件绑定方法并且替换掉以前的方法。

on(events, [selector], [data], fn)

events:一个或多个用空格分隔的事件类型和可选的命名空间,如"click""keydown.myPlugin"

selector:后代选择器的字符串表示形式。[官方原文:]一个选择器字符串用于过滤器的触发事件的选择器元素的后代。如果选择器为null或省略,当它到达选定的元素,事件总是触发。

data:当一个事件被触发时要传递event.data给事件处理函数。

fn:该事件被触发时执行的函数。 false值也可以做一个函数的简写,返回false

3.3 on()的使用

使用on()—事件处理,可以轻松解决本文第2条的问题。

代码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>事件委派</title>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

</head>
    <body>
        <ul id="list">
            <li>1111</li>
            <li>2222</li>
            <li>3333</li>
            <li>4444</li>
        </ul>
        <script type="text/javascript">
            // 第一个参数是绑定在父元素上的事件
            // 第二个参数是要把哪些子元素的同类型事件委派到父元素上
            // 第三个参数是 事件触发后执行的函数
            $("#list").on("mouseover","li",function () {
                // this表示子元素的js对象
                $(this).css("background", "red");
            }).on("mouseout","li",function () {
                $(this).css("background", "white");
            });
            $("#list").append("<li>555</li>\n" +
                "    <li>666</li>");
        </script>
    </body>
</html>

注意:

需要注意的是,这里的on()是无法绑定hober()的,jQueryhover事件是一个封装,只是封装了mouseover和mouseout,所以不能算是一个标准的事件。如果使用mouseover和mouseout两个配合效果也是一样的。代码如下:

$("#list").on("mouseover mouseout", "li", function (event) {
    event = event || window.event;
    if (event.type == "mouseover") {
        $(this).css("background", "red"); // 悬停事件执行代码
    }else if(event.type == "mouseout"){
        $(this).css("background", "white");  // 移出事件执行代码
    }
});

3.4 on()和bind()区别

当第二个参数'selector'null时,on()bind()其实在写法上基本上没有任何区别了,所以我们可以认为on()只是比bind()多了一个可选的'selector'参数,所以on()可以非常方便的换掉bind()

但是bind()仅仅是为选定的元素对象绑定事件,未涉及事件委派,效果与标准事件一样,也有相同的弊端。

另外,从jQuery3.0开始,已经弃用了bind()方法,所以这块内容可以忽略。

bind()演示代码如下:

// 需要为具体的某个子元素绑定事件,也就是标准事件绑定
$("#list li").bind("mouseover",function () {
    $(this).css("background", "red"); 
});
$("#list li").bind("mouseout",function () {
    $(this).css("background", "white");
});

3.5 on()和live()

在1.4之前相信大家非常喜欢使用live(),因为它可以把事件绑定到当前以及以后添加的所有元素上面。当然在1.4之后delegate()也可以 做类似的事情了。

live()的原理很简单,它是通过document进行事件委派的。

传递给 live()的事件处理函数不会绑定在当前元素上,而是把他作为一个特殊的事件处理函数,绑定在 DOM 树的根节点上;当当前元素的相同类型事件被触发时,通过时间委派触发了document对象的事件。

因此我们也可以使用on()通过将事件绑定到document来达到live()一样的效果。

live()写法

<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<!-- 这里建议导入jquery-migrate 1.4.1的版本,保证live()函数未过期,且兼容性好 -->
<script src="https://code.jquery.com/jquery-migrate-1.4.1.min.js"></script>
<script>
    $(function () {
        // 调用live()函数的对象,需要是一个当前元素的对象
        // 但是事件并未绑定到该对象,而是绑定到了document对象上
        // 然后通过事件委派触发当前元素对象
        $("#list li").live("mouseover",function () {
            $(this).css("background", "red");
        });
        $("#list li").live("mouseout",function () {
            $(this).css("background", "white");
        });
        $("#list").append("<li>555</li>\n" +
            "    <li>666</li>");
     });
</script>

on()的写法

<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<!-- 这里建议导入jquery-migrate 1.4.1的版本,保证live()函数未过期,且兼容性好 -->
<!--<script src="https://code.jquery.com/jquery-migrate-1.4.1.min.js"></script>-->
<script>
    $(function () {
        // on可以链式调用,绑定多个事件到一个元素jq对象上
        $(document).on("mouseover","#list li",function () {
            $(this).css("background", "red");
        }).on("mouseout","#list li",function () {
            $(this).css("background", "white");
        });
        $("#list").append("<li>555</li>\n" +
            "    <li>666</li>");
     });
</script>

另外:live()阻止默认行为和冒泡的方式如下:

//返回false,阻止默认行为和冒泡。仅仅调用stopPropagation()无法实现
$("a").live("click", function() { return false; }); //

// 仅仅阻止默认事件行为
$("form").live("submit", function(event){
  event.preventDefault();
});

3.6 on()和delegate()

jQuery-V1.4.2开始,可以使用delegate()替代live()

目的是通过祖先元素来代理委派后代元素的事件绑定问题,某种程度上和live()优点相似。只不过live()是通过document元素委派,而delegate()则可以是任意的祖先节点。

使用delegate()实现代理的写法和on()基本一致,不同的是前两个参数顺序不一样。

delegate()写法

<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
        $(function () {
        // 写法和运行原理与on类似,支持链式调用
        // 不同的是前两个参数顺序不一样
        $("#list").delegate("li", "mouseover", function () {
            $(this).css("background", "red");
        }).delegate("li", "mouseout", function () {
            $(this).css("background", "white");
        });
        $("#list").append("<li>555</li>\n" +
                          "    <li>666</li>");
    });
</script>

on()写法

<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<!-- 这里建议导入jquery-migrate 1.4.1的版本,保证live()函数未过期,且兼容性好 -->
    <script src="https://code.jquery.com/jquery-migrate-1.4.1.min.js"></script>
<script>
        $(function () {
        $("#list").on("mouseover", "li", function () {
            $(this).css("background", "red");
        }).on("mouseout", "li", function () {
            $(this).css("background", "white");
        });
        $("#list").append("<li>555</li>\n" +
                          "    <li>666</li>");
    });
</script>

4. 总结

jQuery推出on() 的目的有2个,一是为了统一接口,二是为了提高性能。所以就用on()替换bind(), live(), delegate()吧。

如果只是绑定一个一次性事件,那接着用one()吧,这个没有变化。

有帮到你吗?有用点一下哈|´・ω・)ノ