一、DOM事件流概念
DOM模型是一个树形结构,在DOM模型中,HTML元素是有层次的。当一个HTML元素上产生一个事件时,该事件会在DOM树中元素节点与根节点之间按特定的顺序传播,路径所通过的节点都会收到该事件,这个传播过程就是DOM事件流。html
DOM事件标准定义了两种事件流,分别是捕获事件流和冒泡事件流。浏览器
1.一、冒泡事件流
默认状况下(使用onclick或者addEventListener来给DOM节点关联事件,默认状况下关联的就是该节点在冒泡事件流中触发的事件),事件使用冒泡事件流。当事件(例如单击事件)在某一DOM元素上被触发时,事件将沿着该节点的各个父结点冒泡穿过整个DOM节点层次。在冒泡过程当中的任什么时候候均可以终止事件的冒泡。若是不中止事件的传播,事件将一直经过DOM冒泡直至到达文档根。函数
冒泡事件流点击哪一个节点就从哪一个节点开始冒泡。当触发下面的DOM节点的事件时,上面节点定义的同类事件也会跟着后面触发。好比下面示例代码:性能
<div id="outer"> <div id="middle"> <div id="inner"> click me! </div> </div> </div> <script> var innerCircle= document.getElementById("inner"); innerCircle.onclick= function () { alert("innerCircle"); }; var middleCircle= document.getElementById("middle"); middleCircle.onclick=function(){ alert("middleCircle"); } var outerCircle= document.getElementById("outer"); outerCircle.onclick= function () { alert("outerCircle"); } </script>
如上图所示:有三层节点,每一个节点都使用 onclick 方式来关联点击事件(该方式关联事件默认该节点是在冒泡事件流中触发事件):spa
当点击最里面的绿色节点时,触发事件顺序:绿色节点的事件 -> 蓝色节点事件 -> 粉色节点事件.net
当点击中间的蓝色节点时,触发事件顺序:蓝色节点事件 -> 粉色节点事件(注意:此时并无触发最里面的绿色节点的事件,由于此时它根本就不在冒泡事件流内)线程
1.二、捕获事件流
与冒泡模型相反,在捕获事件流模型中,事件的处理将从DOM层次的根开始,而不是从触发事件的目标元素开始,事件被从目标元素的全部全部祖先元素依次往下传递。在这个过程当中,事件会被从文档的根到事件目标元素之间各个继承派生的元素所捕获。3d
当使用 addEventListener 方式来定义事件,并把第三个参数设为 true 时,此时节点绑定的事件是在捕获事件流中触发的。事件触发顺序是先触发最外面的绑定同类事件的根节点,而后逐层深刻,直到目标节点的事件。code
三、DOM标准的事件模型
DOM标准同时支持捕获事件模型和冒泡事件模型,可是,捕获事件模型先发生。两种事件流都会触发DOM中的全部对象,从document对象开始,也在document对象结束。htm
示例代码:
<div id="outer"> <div id="middle"> <div id="inner"> click me! </div> </div> </div> <script> var innerCircle = document.getElementById("inner"); innerCircle.addEventListener("click", function () { alert("innerCircle的click事件在捕获阶段被触发"); }, true); innerCircle.addEventListener("click", function () { alert("innerCircle的click事件在冒泡阶段被触发"); }, false); var middleCircle = document.getElementById("middle"); middleCircle.addEventListener("click", function () { alert("middleCircle的click事件在捕获阶段被触发"); }, true); middleCircle.addEventListener("click", function () { alert("middleCircle的click事件在冒泡阶段被触发"); }, false); var outerCircle = document.getElementById("outer"); outerCircle.addEventListener("click", function () { alert("outerCircle的click事件在捕获阶段被触发"); }, true); outerCircle.addEventListener("click", function () { alert("outerCircle的click事件在冒泡阶段被触发"); }, false); </script>
三个节点同时定义了捕获事件和冒泡事件,当事件触发时,先触发捕获事件而后触发冒泡事件。好比:
点击绿色节点:粉色节点捕获事件 -> 蓝色节点捕获事件 -> 绿色节点捕获事件 -> 绿色节点冒泡事件 -> 蓝色节点冒泡事件 -> 粉色节点冒泡事件
点击蓝色节点:粉色节点捕获事件 -> 蓝色节点捕获事件 -> 蓝色节点冒泡事件 -> 粉色节点冒泡事件
以此类推
四、onclick 和 addEventListener 定义事件
使用onclick 定义事件只能给节点绑定一个回调函数,绑定多个回调函数时后面的会覆盖前面定义的事件。而 addEventListener 能够给节点绑定多个回调函数
4.一、addEventListener 定义事件
addEventListener() 方法能够传递三个参数:
element.addEventListener(event, function, boolean)
第三个参数是可选的,用来指定事件是在捕获或冒泡阶段执行。false(默认值):事件句柄在冒泡阶段执行;true:事件句柄在捕获阶段执行
五、阻止冒泡和捕获(event.stopPropagation())
//在onclick中 innerCircle.onclick = function () { alert("innerCircle"); event.stopPropagation(); }; //在addEventListener中 middleCircle.addEventListener("click", function () { alert("middleCircle的click事件在捕获阶段被触发"); event.stopPropagation(); }, true);
节点绑定的事件回调函数执行该方法后,事件就只运行到这里,无论此时是在冒泡仍是捕获阶段,后面由冒泡或者捕获事件流触发的事件都不会触发。
六、阻止事件的默认行为(preventDefault)
有些元素自己在浏览器中有默认行为,好比:连接<a>,提交按钮<input type=”submit”>等,连接<a>的默认动做就是跳转到指定页面。有时候咱们会想要取消元素的默认行为。固然若是元素自己就没有默认行为,那就不必去调用取消默认行为的方法。
//假定有连接<a href="http://www.baidu.com/" id="baidu">百度</a> var a = document.getElementById("baidu"); a.onclick =function(e){ if(e.preventDefault){ //判断浏览器是否非IE浏览器 e.preventDefault(); //非IE浏览器下使用preventDefault方法 }else{ window.event.returnValue == false; ////IE浏览器下令事件(window.event)的returnValue属性为false; } }
七、addEventListener的passive选项
addEventListener 的 passive 选项用在移动端能大大提升性能。好比不少移动端的页面都会监听 touchstart 等 touch 事件,或者滚动onScroll事件,让这些事件发生时触发一些函数。好比:
document.addEventListener("touchstart", function(e){ ... // 可是浏览器并不知道这里会不会有 e.preventDefault() 来阻止默认行为 })
因为 touchstart 事件对象的 cancelable 属性为 true,也就是说它的默认行为能够被经过 preventDefault() 方法阻止,那它的默认行为是什么呢,一般来讲就是滚动当前页面(还多是缩放页面),若是它的默认行为被阻止了,浏览器就必须保持页面静止不动。但浏览器根本没法预先知道一个监听器会不会调用 preventDefault(),它能作的只有等监听器执行完后再去执行默认行为(也就是说浏览器必须等到回调函数执行完而后再去滚动页面),而监听器执行是要耗时的,有些甚至耗时很明显,这样就会致使页面卡顿。即使监听器是个空函数,也会产生必定的卡顿,毕竟空函数的执行也会耗时。
但绝大部分的监听器不会阻止浏览器的默认行为,浏览器至关因而在白等。因此,passive 选项诞生了,passive 表示监听器不会对事件的默认行为说 no,浏览器知道了一个监听器是 passive 的,它就能够在两个线程里同时执行监听器中的 JavaScript 代码和浏览器的默认行为了(也就是能够一边滚动一边执行回调函数里的JS代码了,这样就不会卡顿了),把 passive 选项设为 true 便可告知浏览器该JS代码不会阻止默认行为。
参考:https://blog.csdn.net/hhlljj0828/article/details/79497734?utm_source=blogxgwz5
参考:http://www.javashuo.com/article/p-opdhxsxb-k.html、https://www.cnblogs.com/ycc-020/p/6078968.html