基于事件冒泡、事件捕获和事件委托详解

作者:晏子楚 时间:2024-04-28 09:43:33 

事件冒泡、事件捕获和事件委托

在javascript里,事件委托是很重要的一个东西,事件委托依靠的就是事件冒泡和捕获的机制,我先来解释一下事件冒泡和事件捕获:

事件冒泡会从当前触发的事件目标一级一级往上传递,依次触发,直到document为止。

事件捕获会从document开始触发,一级一级往下传递,依次触发,直到真正事件目标为止。

这么说是不是很抽象,其实就像我敲击了一下键盘,我在敲击键盘的同时,我是不是也敲击了这台电脑,我写个例子大家就明白了:


<!DOCTYPE html>
<html>
   <head>
       <meta charset="UTF-8">
       <title></title>
   </head>
   <style type="text/css">
       #box1 { width: 300px; height: 300px; background: blueviolet; }
       #box2 { width: 200px; height: 200px; background: aquamarine; }
       #box3 { width: 100px; height: 100px; background: tomato; }
       div { overflow: hidden; margin: 50px auto; }
   </style>
   <body>
       <div id="box1">
           <div id="box2">
               <div id="box3"></div>
           </div>
       </div>
       <script>
           function sayBox3() {
               console.log('你点了最里面的box');
           }
           function sayBox2() {
               console.log('你点了最中间的box');
           }
           function sayBox1() {
               console.log('你点了最外面的box');
           }
           // 事件监听,第三个参数是布尔值,默认false,false是事件冒泡,true是事件捕获
           document.getElementById('box3').addEventListener('click', sayBox3, false);
           document.getElementById('box2').addEventListener('click', sayBox2, false);
           document.getElementById('box1').addEventListener('click', sayBox1, false);
       </script>
   </body>
</html>

我们画了三个box,结构是父子关系,分别绑定了打印事件,现在我们来点击最中间的红 * ox:

基于事件冒泡、事件捕获和事件委托详解

我们发现,我们仅仅是点击了红色的box,但是绿色和紫色的box也被触发了打印事件,触犯顺序是 红色>绿色>紫色,这种现象就是事件冒泡了。

我们再试试事件捕获,把上面代码里监听事件的第三个参数改为true,然后点击红色的box:

基于事件冒泡、事件捕获和事件委托详解

我们还是只点击最中间的红 * ox,和上一次一样,也是三个box都触发了事件,但是顺序反过来了,紫色>绿色>红色,这种现象称为事件捕获。

通过上面的例子,应该很容易就理解了事件冒泡和事件捕获,我们平时都是默认冒泡的,冒泡是一直冒到document根文档为止。

现在来谈谈事件委托,事件委托又称之为事件代理,我们通过一个通俗的例子来解释:

有三个同事预计会在周一收到快递,为了签收快递,有两种办法:1.三个人在公司门口等快递;2.委托给前台MM代为签收。现实当中,我们大都采用委托的方案(公司也不会容忍那么多员工站在门口就为了等快递)。前台MM收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台MM也会在收到寄给新员工的快递后核实并代为签收(可以给暂时不存在的节点也绑定上事件)。

我们再举另一个例子:

现在有一个ul,ul里又有100个li,我想给这100个li都绑定一个点击事件,我们一般可以通过for循环来绑定,但是要是有1000个li呢? 为了提高效率和速度,所以我们这时可以采用事件委托,只给ul绑定一个事件,根据事件冒泡的规则,只要你点了ul里的每一个li,都会触发ul的绑定事件,我们在ul绑定事件的函数里通过一些判断,就可以给这100li都触发点击事件了。

具体怎么实现,看代码:


// 这里不讲IE,结尾再说
function clickLi() {
   alert('你点击了li');
}
document.getElementById('isUl').addEventListener('click', function(event) {
   // 每一个函数内都有一个event事件对象,它有一个target属性,指向事件源
   var src = event.target;
   // 我们判断如果target事件源的节点名字是li,那就执行这个函数
   // target里面的属性是非常多的,id名、class名、节点名等等都可以取到
   if(src.nodeName.toLowerCase() == 'li') {
      clickLi() ;
   }
});

这样我们就通过给ul绑定一个点击事件,让所有的li都触发了函数。

那如果想给不同的li绑定不同的函数怎么办?

假设有3个li,我们先写3个不同的函数,再给3个li设置不同的id名,通过判断id名是不是就能给不同的li绑定不同的函数啦:


<body>
   <ul id="isUl">
       <li id="li01">1</li>
       <li id="li02">2</li>
       <li id="li03">3</li>
   </ul>
   <script>
       function clickLi01() {
           alert('你点击了第1个li');
       }
       function clickLi02() {
           alert('你点击了第2个li');
       }
       function clickLi03() {
           alert('你点击了第3个li');
       }
       document.getElementById('isUl').addEventListener('click', function(event) {
           var srcID = event.target.id;
           if(srcID == 'li01'){
               clickLi01();
           }else if(srcID == 'li02') {
               clickLi02();
           }else if(srcID == 'li03') {
               clickLi03();
           }
       });
   </script>
</body>

这就是所谓的事件委托,通过监听一个父元素,来给不同的子元素绑定事件,减少监听次数,从而提升速度。

那么,能不能阻止元素的事件冒泡呢,答案是可以的。

一开始那个例子,假如我们真的只想点击最里面的那个红 * ox,不想另外两个box的事件被触发,我们可以在给红 * ox绑定事件的函数里这么写:


function sayBox3(event) {
   // 阻止冒泡
   event.stopPropagation();
   console.log('你点了最里面的box');
}
document.getElementById('box3').addEventListener('click', sayBox3, false);

这样我们再点击红色的box,那就只会触发它本身的事件啦。

那阻止冒泡有没有实际用途呢?答案是有的,我们看这个例子:

基于事件冒泡、事件捕获和事件委托详解

这是一个模态框,现在的需求是当我们点击红色的按钮需要跳转页面,然后点击白色的对话框不需要任何反应,点其它任何地方就关闭这个模态框。

这里就需要用到阻止冒泡了,红色的按钮是白色对话框的子元素,白色对话框又是这整个模态框的子元素,我们给模态框加上一个点击事件关闭,然后给红色的按钮加上一个点击事件跳转,这时就产生了一个问题,只要点击白色的对话框,由于冒泡机制,这个模态框也会关闭,实际上我们并不想点击白色的对话框有任何反应,这时我们就给这个白色的对话框绑定一个点击事件,函数里写上event.stopPropagation();,这样就OK了。

关于IE

老版本的IE存在兼容问题,根本不支持addEventListener()的添加事件和removeEventListener()的删除事件,它有自己的监听方法:


// 添加事件,事件流固定为冒泡
attachEvent(事件名,事件处理函数)
// 删除事件
detachEvent(事件名,事件处理函数)

还有IE里的事件对象是window.event,事件源是srcElement,阻止冒泡写法也不一样:


function() {
   // IE里阻止冒泡
   window.event.cancelBubble = true;
   // IE里获取事件源的id
   var srcID = window.event.srcElement.id;
}
function(event) {
   // 非IE里阻止冒泡
   event.stopPropagation();
   // 非IE里获取事件源的id
   var srcID = event.target.id;
}

补充

关于js的浏览器兼容问题,一般用能力检测来解决,if(){}else{}

我们平时工作一般都是用jquery,IE这些特殊情况早就帮我们做好兼容啦。

jquery在1.7的版本之后,最流行的事件监听方法是$(元素).on(事件名,执行函数),它还有一种事件委托的写法$(委托给哪个元素).on(事件名,被委托的元素,执行函数)

最后说一点,如果元素被阻止冒泡了,千万别去用事件委托的方式监听事件,因为事件委托的原理是利用事件冒泡,当冒泡被阻止,就无法监听了。

来源:https://www.jianshu.com/p/d3e9b653fa95

标签:冒泡,捕获,委托
0
投稿

猜你喜欢

  • Oracle中的table()函数使用

    2024-01-19 14:50:10
  • python 多进程通信模块的简单实现

    2021-06-13 08:21:32
  • Python源码学习之PyObject和PyTypeObject

    2023-08-11 10:28:45
  • javascript 网站常用的iframe分割

    2023-08-19 09:27:58
  • vue自定义tap指令及tap事件的实现

    2024-05-09 09:25:15
  • mysql insert 存在即不插入语法说明

    2024-01-17 18:46:05
  • replace MYSQL字符替换函数sql语句分享(正则判断)

    2024-01-20 15:07:43
  • vue动态注册组件实例代码详解

    2023-07-02 17:01:11
  • go redis之redigo的使用

    2024-05-22 17:46:37
  • 基于Golang 高并发问题的解决方案

    2024-02-20 16:49:01
  • python爬虫智能翻页批量下载文件的实例详解

    2023-06-20 14:17:12
  • python可迭代类型遍历过程中数据改变会不会报错

    2021-04-15 19:40:52
  • vue绑定class与行间样式style详解

    2024-05-08 10:12:12
  • Go语言转换所有字符串为大写或者小写的方法

    2023-06-21 19:48:07
  • Python 使用 PyQt5 开发的关机小工具分享

    2023-09-15 05:06:02
  • cv2.imread 和 cv2.imdecode 用法及区别

    2023-12-03 06:15:49
  • Oracle 存储过程总结(一、基本应用)

    2024-01-24 22:25:53
  • js格式化金额可选是否带千分位以及保留精度

    2023-10-06 04:03:14
  • python爬虫爬取股票的北上资金持仓数据

    2023-11-18 20:44:56
  • Python装饰器语法糖

    2022-07-10 15:11:34
  • asp之家 网络编程 m.aspxhome.com