一、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