首页 > 编程笔记 > JavaScript笔记

JS事件冒泡

JavaScript 事件流描述的是从页面中接受事件的顺序。IE 和 Netscape 开发团队提出了两个截然相反的事件流概念,IE 的事件流是事件冒泡(event bubbling),Netscape 的事件流是事件捕获(event capturing)。

本节介绍事件冒泡,下节再介绍事件捕获。

事件冒泡:当一个元素接收到事件时,会把它接收到的事件逐级向上传播给它的祖先元素,一直传到顶层的 window 对象(关于最后传播到的顶层对象,不同浏览器有可能不同,例如 IE9 及其以上的 IE、FireFox、Chrome、Safari 等浏览器,事件冒泡的顶层对象为 window 对象,而 IE7/8 顶层对象则为 document对象)。

例如,在 Chrome 浏览器中,当用户单击了<div>元素,click 事件将按照 <div>→<body>→<html>→document→window 的顺序进行传播,如图 1 所示。事件冒泡可以形象地比喻为把一块石头投入水中,泡泡会一直从水底冒出水面,也就是说从下向上开始传播。
事件冒泡时事件的传播顺序
图 1:事件冒泡时事件的传播顺序

事件冒泡对所有浏览器都是默认存在的,且由元素的 HTML 结构决定,而不是由元素在页面中的位置决定,所以即便使用定位或浮动使元素脱离父元素的范围,单击元素时,其依然存在冒泡现象。

使用 addEventListener() 绑定事件,当第三个参数为 false 时,事件为冒泡;为 true 时,事件为捕获。而使用事件源对象的事件属性绑定事件函数以及使用 HTML 标签事件属性绑定事件函数的事件流都是事件冒泡。

【例 1】事件冒泡演示。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>事件冒泡演示</title>
<style>
     div{padding:30px;}
     #div1{background:red;}
     #div2{background:green;}
     #div3{background:blue;position:absolute;top:200px;}
</style>
<script>
     window.onload = function(){
          //获取各个元素
          var oBody = document.getElementById('body1');
          var oDiv1 = document.getElementById('div1');
          var oDiv2 = document.getElementById('div2');
          var oDiv3 = document.getElementById('div3');
          //对各个元素的单击事件绑定事件处理函数fn1
          window.onclick = fn1;
          document.onclick = fn1;
          oBody.onclick = fn1;
          oDiv1.onclick = fn1;
          //oDiv2.onclick = fn1;
          oDiv3.onclick = fn1               
          function fn1(){//定义事件处理函数
               console.log(this);
          }
     };
</script>
</head>
<body id="body1">
     <div id="div1">    
          <div id="div2">
               <div id="div3"></div>
          </div>
     </div>
</body>
</html>
例 1 对获取到的各个元素都使用事件属性绑定事件处理函数,因而这些元素都会实现事件冒泡。当单击 div3 时,div3 作为事件冒泡的最低层元素,会首先触发单击事件,在 Chrome 浏览器中的运行结果如图 2 所示。
事件冒泡运行结果
图 2:事件冒泡运行结果

由图 2 可见,虽然 div3 因为绝对定位而脱离了文档,但其所触发的事件仍然会逐级向上传递单击事件给 div2、div1、document 和 window,即便注释掉 div2 的单击事件。

在前面介绍了,addEventListener() 的第三个参数取 false 值时,将会实现事件冒泡。下面通过示例 2 演示使用 addEventListener() 实现事件冒泡。

【例 2】使用 addEventListener() 实现事件冒泡。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>使用addEventListener()实现事件冒泡</title>
<style>
     div{padding:30px;}
     #div1{background:red;}
     #div2{background:green;}
     #div3{background:blue;position:absolute;top:200px;}
</style>
<script>
     window.onload = function(){
          var oDiv1 = document.getElementById('div1');
          var oDiv2 = document.getElementById('div2');
          var oDiv3 = document.getElementById('div3');
          //调用addEventListener()实现事件冒泡
          oDiv1.addEventListener('click',fn1,false);
          oDiv2.addEventListener('click',fn1,false);
          oDiv3.addEventListener('click',fn1,false);
    
          function fn1(){
               console.log(this);
          }
     };
</script>
</head>
<body id="body1">
     <div id="div1">    
          <div id="div2">
               <div id="div3"></div>
          </div>
     </div>
</body>
</html>
例 2 中 div1、div2 和 div3 元素均使用第三个参数为 false 的 addEventListener() 绑定事件函数,因而这 3 个元素将实现事件冒泡。在 Chrome 浏览器中运行后,当单击 div3 时,div3 作为事件冒泡的最低层元素,会首先触发单击事件,然后 div3 逐级向上传递单击事件给 div2 和 div1。

例 2 在 Chrome 浏览器中的运行结果如图 3 所示。由图 3 可见事件的接受顺序为 div3→div2→div1。
使用 addEventListener() 实现事件冒泡
图 3:使用 addEventListener() 实现事件冒泡

事件冒泡在实际应用中有时会给我们带来便利,例如示例 3 的“分享”功能就是使用了事件冒泡来实现的。

【例 3】使用事件冒泡实现“分享”功能。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>使用事件冒泡实现"分享"功能</title>
<style>
     #div1{width:80px;height:150px;border:black 1px solid;position:absolute;
            left:-82px;top:100px;}
     #div2{width:30px;height:70px;position:absolute;right:-30px;top:45px;
            background:black;color:white;text-align:center;}
     ul{list-style:none;padding:0 20px;}
     img{width:36px;height:39px;}
</style>
<script>
     window.onload = function(){
        var oDiv = document.getElementById('div1');
        oDiv.onmouseover = function(){//鼠标光标移入,使div1显示
            this.style.left = '0px';
        }
        oDiv.onmouseout = function(){//鼠标光标移出,使div1隐藏
            this.style.left = '-82px';
        }
    };
</script>
</head>
<body>
  <div id="div1">
    <ul>
      <a href="#"><li><img src="images/qq.png"/></li></a>
      <a href="#"><li><img src="images/sina.png"/></li></a>
      <a href="#"><li><img src="images/renren.png"/></li></a>
    </ul>
    <div id="div2">分享到</div>
  </div>
</body>
</html>
例 3 在 Chrome 浏览器中运行后的初始状态如图 4 所示。
初始状态
图 4:初始状态

当我们将鼠标光标移到 div2(“分享到”)上后,div2 的鼠标光标移入事件将会传递给它的父级元素 div1(图标列表),因而此时会触发 div1 的鼠标光标移入事件,而使 div1 显示出来,结果如图 5 所示。
鼠标光标移入“分享到”后的状态
图 5:鼠标光标移入“分享到”后的状态

当将鼠标光标从 div2 移开时,div2 的鼠标光标移开事件同样会传递给 div1,从而触发 div1 的鼠标光标移开事件,而使 div1 隐藏起来,即回到图 4 所示状态。

在程序开发时,事件冒泡在某些时候会带来便利,但有时却又会带来不好的影响,此时就需要阻止事件冒泡。所有标准浏览器(IE9 及其以上版本、Chrome 和 Firefox)都通过事件对象调用 stopPropagation() 来实现事件冒泡的阻止。

注:IE7/8 等非标准的 IE 只能使用设置事件对象的 cancelBubble 属性值为 true 的方法来阻止事件冒泡。

阻止事件冒泡的方法格式如下:

事件对象.stopPropagation();//针对标准浏览器


下面通过示例 4 来演示事件冒泡的阻止。

【例 4】事件冒泡的阻止。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>阻止事件冒泡</title>
<style>
     #div1{width:100px;height:200px;border:1px solid red;display:none;}
</style>
<script>
     window.onload = function(){
          var oBtn = document.getElementById('btn');
          var oDiv = document.getElementById('div1');
          oBtn.onclick = function(ev){
               //阻止事件冒泡的兼容处理:
               ev.stopPropagation();
               oDiv.style.display = 'block';//显示div
          };
          document.onclick = function(){
               oDiv.style.display = 'none';//隐藏div
          }
     };
</script>
</head>
<body>
     <div id="div1"></div>
    <input type="button" id="btn" value="显示DIV"/>
</body>
</html>
例 4 的作用是单击按钮时,显示 div,如图 6 所示;
单击按钮显示div
图 6:单击按钮显示 div

再单击除按钮以外的其他地方则隐藏 div,如图 7 所示。
单击除按钮以外的其他地方隐藏div
图 7:单击除按钮以外的其他地方隐藏 div

由示例 JS 代码,可知 button 和 document 两个元素都使用了事件属性来绑定单击事件处理函数,因而它们将会实现事件冒泡。因而当单击按钮时,如果不阻止 button 元素的事件冒泡,button 触发的单击事件将会传递到 document。

根据事件冒泡的事件接受顺序,可知单击按钮后将显示 div,但因为事件冒泡,document 接着也触发了单击事件,而其却是隐藏 div,这样最终的运行结果是,单击按钮后将永远都无法显示 div。可见,要实现我们所要求的功能,就必须在单击按钮后,只有 button 触发单击事件,这就需要阻止 button 的事件冒泡。

所有教程

优秀文章