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 所示。事件冒泡可以形象地比喻为把一块石头投入水中,泡泡会一直从水底冒出水面,也就是说从下向上开始传播。
事件冒泡对所有浏览器都是默认存在的,且由元素的 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 可见,虽然 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。
事件冒泡在实际应用中有时会给我们带来便利,例如示例 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 所示。
当我们将鼠标光标移到 div2(“分享到”)上后,div2 的鼠标光标移入事件将会传递给它的父级元素 div1(图标列表),因而此时会触发 div1 的鼠标光标移入事件,而使 div1 显示出来,结果如图 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,如图 7 所示。
由示例 JS 代码,可知 button 和 document 两个元素都使用了事件属性来绑定单击事件处理函数,因而它们将会实现事件冒泡。因而当单击按钮时,如果不阻止 button 元素的事件冒泡,button 触发的单击事件将会传递到 document。
根据事件冒泡的事件接受顺序,可知单击按钮后将显示 div,但因为事件冒泡,document 接着也触发了单击事件,而其却是隐藏 div,这样最终的运行结果是,单击按钮后将永远都无法显示 div。可见,要实现我们所要求的功能,就必须在单击按钮后,只有 button 触发单击事件,这就需要阻止 button 的事件冒泡。