摘自W3school
JavaScript 使我们有能力创建动态页面。事件是可以被 JavaScript 侦测到的行为。
网页中的每个元素都可以产生某些可以触发 JavaScript 函数的事件。比方说,我们可以在用户点击某按钮时产生一个 onClick 事件来触发某个函数。事件在 HTML 页面中定义。
事件流描述的是从页面中接收事件的顺序。不过IE(微软)提出的是冒泡流;网景提出的是捕获流。
IE提出的事件流叫做事件冒泡,即事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点,自下而上去触发事件,可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。所有现代的浏览器都支持事件冒泡。下面为一个冒泡实例:
<div id="parent">
parent
<div id="child">childdiv>
div>
<script>
var p = document.getElementById('parent');
var c = document.getElementById('child');
// 先弹出c后是p
c.onclick = function(e){alert('c')};
p.onclick = function(){alert('p')};
script>
网景公司提出的事件流叫做事件捕获,和事件冒泡相反,即事件会从最外层开始发生,直到最具体的元素,自上而下去触发事件。但是老版本的浏览器是不支持的,所以默认还是冒泡。下面为一个捕获实例:
<div id="parent">
parent
<div id="child">childdiv>
div>
<script>
var p = document.getElementById('parent');
var c = document.getElementById('child');
// 先弹出p后是c
p.addEventListener('click', function(){alert("p")}, true);
c.addEventListener('click', function(){alert("c")}), true;
script>
又分为DOM0/DOM2/DOM3级事件。
DOM2级事件规定的事件流包含3个阶段,事件捕获阶段、处于目标阶段、事件冒泡阶段。首先发生的事件捕获为截获事件提供机会,然后是实际的目标接收事件,最后一个阶段是事件冒泡阶段,可以在这个阶段对事件做出响应。事件模型,如下图:
由上图可知:
1. 一个完整的JS事件流是从window开始,最后回到window的一个过程
2. 事件流被分为三个阶段(1~5)捕获过程、(5~6)目标过程、(6~10)冒泡过程
示例:
<div id="parent">
parent
<div id="child">childdiv>
div>
<script>
var p = document.getElementById('parent'),
c = document.getElementById('child');
/*
点击子节点依次的触发顺序为:window捕获,父节点捕获,子节点捕获,子节点冒泡,父节点冒泡,window冒泡
点击父节点依次的触发顺序为:window捕获,父节点捕获,父节点冒泡,window冒泡
target/currentTarget/eventPhase 都是event里面的属性。
target是真正发生事件的DOM元素,currentTarget是当前事件发生在哪个DOM元素上,eventPhase表示当前所处阶段。
eventPhase值为1/2/3,分别表示为:捕获阶段/目标阶段/冒泡阶段
目标阶段指target === currentTarget或者eventPhase === 2的时候。
window没有nodeName,所以为undefined
*/
c.addEventListener('click', function (e) {
console.log('子节点捕获', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
}, true);
c.addEventListener('click', function (e) {
console.log('子节点冒泡', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
}, false);
p.addEventListener('click', function (e) {
console.log('父节点捕获', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
}, true);
p.addEventListener('click', function (e) {
console.log('父节点冒泡', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
}, false);
window.addEventListener('click', function (e) {
console.log('window捕获', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
}, true);
window.addEventListener('click', function (e) {
console.log('window冒泡', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
});
script>
e.stopPropagation()或者e.stopImmediateProgation()或者设置e.cancelBubble = true;均可阻止事件冒泡。
e.cancelBubble = true
适用老牌IE浏览器,不过现在主流浏览器也支持,效果同e.stopPropagation();
e.stopPropagation()
可阻止事件的向上继续传播,但不会阻止该元素绑定的其他事件的执行;
e.stopImmediateProgation()
做了两件事,先是阻止绑定在该元素上的其他事件,再阻止其向上传播。
<div id="parent">
parent
<div id="child">childdiv>
div>
<script>
var p = document.getElementById('parent');
var c = document.getElementById('child');
// 第一种情况:先c后p,默认冒泡
c.onclick = function(e){alert('c')};
p.onclick = function(){alert('p')};
// /*
// 第二种情况:先p后c
// 原理分析:addEventListener(event, function, boolean)属于DOM2级事件,但有兼容性问题;
// 对于方法的兼容性查看,可去can i use 即caniuse.com查看。
// 第三个参数默认不填(即为false)表示冒泡,填写true则表示捕获;
// 它允许给同一个事件注册多个监听器,下面第五种情况会涉及到。
// */
// p.addEventListener('click', function(){alert("p")}, true);
// c.addEventListener('click', function(){alert("c")}), true;
// // 第三种情况:只有c,三种方法都可阻止冒泡
// // c.onclick = function(e){alert('c'); e.cancelBubble = true};
// // c.onclick = function(e){alert('c'); e.stopImmediatePropagation();};
// c.onclick = function(e){alert('c'); e.stopPropagation();};
// p.onclick = function(){alert('p')};
// /*
// 第四种情况:先c后p
// 原因分析:onclick属于DOM0事件,如果同一个元素绑定多个事件,会被后者覆盖
// */
// // c.onclick = function(e){alert('c'); e.cancelBubble = true};
// // c.onclick = function(e){alert('c'); e.stopPropagation();};
// c.onclick = function(e){alert('c'); e.stopImmediatePropagation();};
// c.onclick = function(e){alert('c')};
// p.onclick = function(){alert('p')};
// // 第五种情况:先c后ccc,绑定的后续事件不能被阻止
// // c.addEventListener('click', function(e){alert("c"); e.cancelBubble = true;});
// c.addEventListener('click', function(e){alert("c"); e.stopPropagation();});
// c.addEventListener('click', function(e){alert("ccc")});
// p.addEventListener('click', function(){alert("p")});
// // 第六种情况:只有c,绑定的后续事件被阻止
// c.addEventListener('click', function(e){alert("c"); e.stopImmediatePropagation();});
// c.addEventListener('click', function(e){alert("ccc")});
// p.addEventListener('click', function(){alert("p")});
script>
所谓的默认行为就比如是:点击a标签的时候就跳转;点击表单的提交按钮的时候就会提交表单等。有时候不希望这些事发生,所以就要阻止默认行为了。
1. 使用w3c的e.preventDefault()
方法,在IE中使用e.returnValue = false
。可阻止默认行为,但不会阻止冒泡,要想阻止冒泡再添加相应的阻止冒泡逻辑;
2. 在原生js中也可使用return false
阻止默认行为,但是该方法在 用addEventListener/attachEvent绑定事件的时候,不生效,必须使用方法1;
3. 在jQuery中使用return false
即可阻止默认行为又可阻止冒泡。
实现逻辑:
<div id='parent'>
<a href="http://www.baidu.com" id="a">childa>
div>
<script>
var p = document.getElementById('parent');
var a = document.getElementById('a');
// 正常情况:先弹a,后p,最后跳转
a.onclick = function (e) {
alert('弹框关闭后,页面会跳转');
};
// // 方法1
// a.onclick = function (e) {
// alert('弹框关闭后,也不会跳转');
// if(e.preventDefault){
// e.preventDefault();
// }else{
// window.event.returnValue == false;
// }
// // e.stopPropagation()
// };
// // 方法2 不会跳转
// a.onclick = function (e) {
// alert('弹框关闭后,也不会跳转');
// e.stopPropagation();
// return false;
// };
// // 方法2 仍会跳转
// a.addEventListener('click', function (e) {
// alert('弹框关闭后,也不会跳转了');
// // e.preventDefault();
// return false;
// });
// // 方法3
// $('a').on('click', function () {
// alert('弹框关闭后,也不会跳转了');
// return false;
// });
p.onclick = function(){alert('p')};
script>