dom事件流
事件捕获(event capturing)
当使用事件捕获时
| |
---------------| |-----------------
| outer | | |
| -----------| |----------- |
| |inner \ / | |
| ------------------------- |
| Event CAPTURING |
-----------------------------------
outer
上的事件处理器先触发, 然后是inner
上的
事件冒泡(event bubbling)
/ \
---------------| |-----------------
| outer | | |
| -----------| |----------- |
| |inner | | | |
| ------------------------- |
| Event BUBBLING |
-----------------------------------
与事件捕获相反, 当使用事件冒泡时, inner上的事件处理器先被触发, 其后是outer上面的。
一般来说事件冒泡机制,用的更多一些,所以在IE8以及之前,IE只支持事件冒泡。IE9+/FF/Chrome这2种模型都支持,可以通过addEventListener((type, listener, useCapture)的useCapture来设定,useCapture=false代表着事件冒泡,useCapture=true代表着采用事件捕获。默认是false,即事件冒泡。
DOM事件流
DOM事件流:将事件分为三个阶段:捕获阶段、目标阶段、冒泡阶段。先调用捕获阶段的处理函数,其次调用目标阶段的处理函数,最后调用冒泡阶段的处理函数。
我们在outC上触发onclick事件(这个是目标对象),如果我们在outC上同时绑定捕获阶段/冒泡阶段事件处理函数会怎么样呢?
<script> window.onload = function(){ var outA = document.getElementById("outA"); var outB = document.getElementById("outB"); var outC = document.getElementById("outC"); // 目标(自身触发事件,是冒泡还是捕获无所谓) outC.addEventListener('click',function(){alert("target2");},true); outC.addEventListener('click',function(){alert("target1");},true); // 事件冒泡 outA.addEventListener('click',function(){alert("bubble1");},false); outB.addEventListener('click',function(){alert("bubble2");},false); // 事件捕获 outA.addEventListener('click',function(){alert("capture1");},true); outB.addEventListener('click',function(){alert("capture2");},true); }; </script> <body> <div id="outA" style="width:400px; height:400px; background:#CDC9C9;position:relative;"> <div id="outB" style="height:200; background:#0000ff;top:100px;position:relative;"> <div id="outC" style="height:100px; background:#FFB90F;top:50px;position:relative;"></div> </div> </div> </body>
点击outC的时候,打印顺序是:capture1-->capture2-->target2-->target1-->bubble2-->bubble1。由于outC是我们触发事件的目标对象,在outC上注册的事件处理函数,属于DOM事件流中的目标阶段。目标阶段函数的执行顺序:先注册的先执行,后注册的后执行。这就是上面我们说的,在目标对象上绑定的函数是采用捕获,还是采用冒泡,都没有什么关系,因为冒泡和捕获只是对父元素上的函数执行顺序有影响,对自己没有什么影响。如果不信,可以将下面的代码放进去验证。
// 目标(自身触发事件,是冒泡还是捕获无所谓) outC.addEventListener('click',function(){alert("target1");},false); outC.addEventListener('click',function(){alert("target2");},true); outC.addEventListener('click',function(){alert("target3");},true); outC.addEventListener('click',function(){alert("target4");},false);
至此我们可以给出事件函数执行顺序的结论了:捕获阶段的处理函数最先执行,其次是目标阶段的处理函数,最后是冒泡阶段的处理函数。目标阶段的处理函数,先注册的先执行,后注册的后执行。
阻止默认事件
1. 阻止冒泡事件
主要是用于阻止事件传播。阻止它被分派到其他的DOM节点上,在事件传播的任何阶段都能使用。使用方法如下(兼容IE):
function stopBubble(event){ if(window.event){//兼容IE window.event.cancelBubble=true; }else{ event.stopPropagation(); }
2. 阻止默认事件
像submit这类的表单元素,都会绑定默认事件,如果不阻止默认事件,则绑定的其他方法也会无效。使用方法如下(兼容IE):
function stopDefaultEvent(event){ if(window.event){//兼容IE window.event.returnValue=false; }else{ event.preventDefault() } return false; }
事件委托、时间代理
上面的例子是要阻止冒泡,有时候冒泡机制也可以被利用。先看一个问题:
假设要在页面上放在 10^n(n>=1) 个列表项元素,当我点击某个元素时,需要输出点击的是第几个。
一般做法是,遍历时给每个元素绑定点击事件:
var li = document.getElementsByTagName('li');
for(var i=0; i<li.length; i++){
li[i].setAttribute('i',i+1);
li[i].addEventListener('click', function(e){
var b = this.getAttribute('i');
console.log('这是第' + b + '个<li>元素');
});
}
另一种方法是,可以利用冒泡机制,在父元素只绑定一次点击事件:
var li = document.getElementsByTagName('li');
for(var i=0; i<li.length; i++){
li[i].setAttribute('i',i+1);
}
var ul = document.getElementById('wrapper');
ul.addEventListener('click', function(e){
if(e.target && e.target.nodeName.toUpperCase() === 'LI'){
var b = e.target.getAttribute('i');
console.log('这是第' + b + '个<li>元素');
}
});
这种事件委托的方式减少了事件处理程序,也能降低程序的复杂性和出错概率。
参考:
https://www.jb51.net/article/94394.htm
https://blog.csdn.net/michael8512/article/details/77447537