Javascript 事件冒泡机制详细介绍


Posted in Javascript onOctober 10, 2016

1. 事件

         在浏览器客户端应用平台,基本生都是以事件驱动的,即某个事件发生,然后做出相应的动作。

         浏览器的事件表示的是某些事情发生的信号。事件的阐述不是本文的重点,尚未了解的朋友,可以访问W3school教程 进行了解,这将有助于更好地理解以下的内容 。

2.冒泡机制

            什么是冒泡呢?

         下面这个图片大家应该心领神会吧,气泡从水底开始往上升,由深到浅,升到最上面。在上升的过程中,气泡会经过不同深度层次的水。                       

             Javascript 事件冒泡机制详细介绍

相对应地:这个气泡就相当于我们这里的事件,而水则相当于我们的整个dom树;事件从dom 树的底层 层层往上传递,直至传递到dom的根节点。

简单案例分析

下面通过一个简单的例案例来阐述冒泡原理: 

       定义一个html, 里面有三个简单的dom 元素:div1,div2, span,div2包含span,div1 包含div2;而它们都在body 下:    

<body id="body">
 <div id="box1" class="box1">
 <div id="box2" class="box2">
  <span id="span">This is a span.</span>
 </div>
 </div>
</body>

界面原型如下:

                             Javascript 事件冒泡机制详细介绍

       在这个基础上,我们实现下面的功能:

         a.body添加 click 事件监听,当body捕获到event事件时,打印出事件发生的时间和 触发事件的节点信息:

<script type="text/javascript">
 window.onload = function() {
 document.getElementById("body").addEventListener("click",eventHandler);
 }
 function eventHandler(event) {
 console.log("时间:"+new Date(event.timeStamp)+" 产生事件的节点:" + event.target.id +" 当前节点:"+event.currentTarget.id);
 }
</script>

当我们依次点击"This is span",div2,div1,body后,输出以下信息:

           Javascript 事件冒泡机制详细介绍

分析以上的结果:

              无论是body,body 的子元素div1,还是 div的子元素div2,还有 span, 当这些元素被点击click时,都会产生click事件,并且body都会捕获到,然后调用相应的事件处理函数。就像水中的气泡从底往上冒一样,事件也会往上传递。

             事件传递的示意图如下所示:

           Javascript 事件冒泡机制详细介绍

         一般地,事件在传递过程中会有一些信息,这些是事件的组成部分:事件发生的时间+事件发生的地点+ 事件的类型+事件的当前处理者+其他信息,

      Javascript 事件冒泡机制详细介绍
     

完整的html代码如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript" src="js/jquery-1.11.0.js"></script>
<title>Insert title here</title>
<style type="text/css">
.box1 {
 border: green 40px solid;
 width: 300px;
 height: 300px;
 margin: auto;
}

.box2 {
 border: yellow 40px solid;
 width: 220px;
 height: 220px;
 margin: auto;
}

span {
 position: relative;
 left: 50px;
 top: 50px;
 background-color: rgba(128, 128, 128, 0.22);
}
</style>

<script type="text/javascript">
 window.onload = function() {
 document.getElementById("body").addEventListener("click",eventHandler);
 }
 function eventHandler(event) {
 console.log("时间:"+new Date(event.timeStamp)+" 产生事件的节点:" + event.target.id +" 当前节点:"+event.currentTarget.id);
 }
</script>

</head>
<body id="body">
 <div id="box1" class="box1">
 <div id="box2" class="box2">
  <span id="span">This is a span.</span>
 </div>
 </div>
</body>
</html>

  b.终止事件的冒泡

    我们现在想实现这样的功能,在div1 点击的时候,弹出 "你好,我是最外层div。",点击div2 的时候,弹出 "你好,我是第二层div";点击span 的时候,弹出"您好,我是span。"。

    由此我们会有下面的javascript片段:

<script type="text/javascript">
 window.onload = function() {
 document.getElementById("box1").addEventListener("click",function(event){
  alert("您好,我是最外层div。");
 });
 document.getElementById("box2").addEventListener("click",function(event){
  alert("您好,我是第二层div。");
 });
 document.getElementById("span").addEventListener("click",function(event){
  alert("您好,我是span。");
 });
 }
</script>

     预期上述代码会单击span 的时候,会出来一个弹出框 "您好,我是span。" 是的,确实弹出了这样的对话框:

          Javascript 事件冒泡机制详细介绍

     然而,不仅仅会产生这个对话框,当点击确定后,会依次弹出下列对话框:

           Javascript 事件冒泡机制详细介绍     Javascript 事件冒泡机制详细介绍
  

这显然不是我们想要的! 我们希望的是点谁显示谁的信息而已。为什么会出现上述的情况呢? 原因就在于事件的冒泡,点击span的时候,span 会把产生的事件往上冒泡,作为父节点的div2 和 祖父节点的div1也会收到此事件,于是会做出事件响应,执行响应函数。现在问题是发现了,但是怎么解决呢?

       方法一:我们来考虑一个形象一点的情况:水中的一个气泡正在从底部往上冒,而你现在在水中,不想让这个气泡往上冒,怎么办呢?——把它扎破!没了气泡,自然不会往上冒了。类似地,对某一个节点而言,如果不想它现在处理的事件继续往上冒泡的话,我们可以终止冒泡:

         在相应的处理函数内,加入  event.stopPropagation()   ,终止事件的广播分发,这样事件停留在本节点,不会再往外传播了。修改上述的script片段:

<script type="text/javascript">
 window.onload = function() {
 document.getElementById("box1").addEventListener("click",function(event){
  alert("您好,我是最外层div。");
  event.stopPropagation();
 });
 document.getElementById("box2").addEventListener("click",function(event){
  alert("您好,我是第二层div。");
  event.stopPropagation();
 });
 document.getElementById("span").addEventListener("click",function(event){
  alert("您好,我是span。");
  event.stopPropagation();
 });
 }
</script>

             经过这样一段代码,点击不同元素会有不同的提示,不会出现弹出多个框的情况了。

           方法二:事件包含最初触发事件的节点引用 和 当前处理事件节点的引用,那如果节点只处理自己触发的事件即可,不是自己产生的事件不处理。event.target 引用了产生此event对象的dom 节点,而event.currrentTarget 则引用了当前处理节点,我们可以通过这 两个target 是否相等。

            比如span 点击事件,产生一个event 事件对象,event.target 指向了span元素,span处理此事件时,event.currentTarget 指向的也是span元素,这时判断两者相等,则执行相应的处理函数。而事件传递给 div2 的时候,event.currentTarget变成 div2,这时候判断二者不相等,即事件不是div2 本身产生的,就不作响应处理逻辑。               

<script type="text/javascript">
 window.onload = function() {
 document.getElementById("box1").addEventListener("click",function(event){
  if(event.target == event.currentTarget)
  {
    alert("您好,我是最外层div。");
  }
 });
 document.getElementById("box2").addEventListener("click",function(event){
  if(event.target == event.currentTarget)
  {
  alert("您好,我是第二层div。");
  }
 });
 document.getElementById("span").addEventListener("click",function(event){
  if(event.target == event.currentTarget)
  {
    alert("您好,我是span。");
  
  }
 });
 }
</script>

         比较:

         从事件传递上看:方法一在于取消事件冒泡,即当某些节点取消冒泡后,事件不会再传递;方法二在于不阻止冒泡,过滤需要处理的事件,事件处理后还会继续传递;

         优缺点:

         方法一缺点:为了实现点击特定的元素显示对应的信息,方法一要求每个元素的子元素也必须终止事件的冒泡传递,即跟别的元素功能上强关联,这样的方法会很脆弱。比如,如果span 元素的处理函数没有执行冒泡终止,则事件会传到div2 上,这样会造成div2 的提示信息;

        方法二缺点:方法二为每一个元素都增加了事件监听处理函数,事件的处理逻辑都很相似,即都有判断 if(event.target == event.currentTarget),这样存在了很大的代码冗余,现在是三个元素还好,当有10几个,上百个又该怎么办呢?
还有就是为每一个元素都有处理函数,在一定程度上增加逻辑和代码的复杂度。

        我们再来分析一下方法二:方法二的原理是 元素收到事件后,判断事件是否符合要求,然后做相应的处理,然后事件继续冒泡往上传递;
        既然事件是冒泡传递的,那可不可以让某个父节点统一处理事件,通过判断事件的发生地(即事件产生的节点),然后做出相应的处理呢?答案是可以的,下面通过给body 元素添加事件监听,然后通过判断event.target 然后对不同的target产生不同的行为。

        将方法二的代码重构一下:

<script type="text/javascript">
 window.onload = function() {
 document.getElementById("body").addEventListener("click",eventPerformed);
 }
 function eventPerformed(event) {
 var target = event.target;
 switch (target.id) {
 case "span": 
  alert("您好,我是span。");
  break;
 case "div1":
  alert("您好,我是第二层div。");
  break;
 case "div2":
  alert("您好,我是最外层div。");
  break;
 }
 }
</script>

            结果会是点击不同的元素,只弹出相符合的提示,不会有多余的提示。

           通过以上方式,我们把本来每个元素都要有的处理函数,都交给了其祖父节点body 元素来完成了,也就是说,span,div2,div1 将自己的响应逻辑委托给body,让它来完成相应逻辑,自己不实现相应逻辑,这个模式,就是所谓的事件委托。

          下面是一个示意图:

Javascript 事件冒泡机制详细介绍

     关于事件代理的问题,以后继续探讨。

    感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

Javascript 相关文章推荐
jquery.ui.progressbar 中文文档
Nov 26 Javascript
ExtJS Ext.MessageBox.alert()弹出对话框详解
Apr 02 Javascript
EXTjs4.0的store的findRecord的BUG演示代码
Jun 08 Javascript
简介JavaScript中toTimeString()方法的使用
Jun 12 Javascript
使用jQuery mobile库检测url绝对地址和相对地址的方法
Dec 04 Javascript
浅谈JS中的bind方法与函数柯里化
Aug 10 Javascript
jQuery Ajax使用FormData对象上传文件的方法
Sep 07 Javascript
获取今天,昨天,本周,上周,本月,上月时间(实例分享)
Jan 04 Javascript
JS字符串false转boolean的方法(推荐)
Mar 08 Javascript
在React中如何优雅的处理事件响应详解
Jul 24 Javascript
Vue框架TypeScript装饰器使用指南小结
Feb 18 Javascript
基于VUE实现判断设备是PC还是移动端
Jul 03 Javascript
JS 滚动事件window.onscroll与position:fixed写兼容IE6的回到顶部组件
Oct 10 #Javascript
javaScript 事件绑定、事件冒泡、事件捕获和事件执行顺序整理总结
Oct 10 #Javascript
jQuery EasyUI tree 使用拖拽时遇到的错误小结
Oct 10 #Javascript
jQuery继承extend用法详解
Oct 10 #Javascript
mvc 、bootstrap 结合分布式图简单实现分页
Oct 10 #Javascript
针对后台列表table拖拽比较实用的jquery拖动排序
Oct 10 #Javascript
KnockoutJS 3.X API 第四章之数据控制流foreach绑定
Oct 10 #Javascript
You might like
关于php连接mssql:pdo odbc sql server
2011/07/20 PHP
PHP生成随机密码方法汇总
2015/08/27 PHP
详解php用curl调用接口方法,get和post两种方式
2017/01/13 PHP
收藏Javascript中常用的55个经典技巧
2007/08/12 Javascript
jQuery 技巧大全(新手入门篇)
2009/05/12 Javascript
读jQuery之九 一些瑕疵说明
2011/06/21 Javascript
jQuery选择器中含有空格的使用示例及注意事项
2013/08/25 Javascript
如何实现修改密码时密码框显示保存到cookie的密码
2013/12/10 Javascript
Bootstrap select下拉联动(jQuery cxselect)
2017/01/04 Javascript
JavaScript事件方法(实例讲解)
2017/06/27 Javascript
移动端手指操控左右滑动的菜单
2019/09/08 Javascript
vue实现输入一位数字转汉字功能
2019/12/13 Javascript
js实现车辆管理系统
2020/08/26 Javascript
简明 Python 基础学习教程
2007/02/08 Python
python增加矩阵维度的实例讲解
2018/04/04 Python
Python实现多态、协议和鸭子类型的代码详解
2019/05/05 Python
Pytorch反向求导更新网络参数的方法
2019/08/17 Python
python使用pygame实现笑脸乒乓球弹珠球游戏
2019/11/25 Python
Django models filter筛选条件详解
2020/03/16 Python
Python3安装模块报错Microsoft Visual C++ 14.0 is required的解决方法
2020/07/28 Python
详解python中的三种命令行模块(sys.argv,argparse,click)
2020/12/15 Python
Lacoste澳大利亚官网:服装、鞋类及配饰
2018/11/14 全球购物
如何写出高质量、高性能的MySQL查询
2014/11/17 面试题
地理科学专业毕业生求职信
2013/10/15 职场文书
房地产项目策划书
2014/02/05 职场文书
销售行政专员岗位职责
2014/06/10 职场文书
民族学专业求职信
2014/07/28 职场文书
社区关爱留守儿童活动方案
2014/08/22 职场文书
小学国旗下的演讲稿
2014/08/28 职场文书
焦裕禄精神心得体会
2014/09/02 职场文书
村党支部书记个人对照材料汇报
2014/10/26 职场文书
2016寒假社会实践心得体会范文
2015/10/09 职场文书
装修安全责任协议书
2016/03/22 职场文书
幼师必备:幼儿园期末教师评语50条
2019/11/01 职场文书
Python实现位图分割的效果
2021/11/20 Python
linux目录管理方法介绍
2022/06/01 Servers