最近在複習前端的基礎,看到事件這一節的時候,恰好發現了筆記中一道特別好玩而且十分有趣的代碼,根據這麼一道題目,基本上可以把事件冒泡和事件捕獲的盲區給掃空。本文就帶你一塊兒來看看這段有趣的代碼。javascript
可是,首先咱們仍是要例行公事,把一些基礎的概念過一過,其實也不須要太長的時間。css
JavaScript
和HTML
之間的交互是經過事件實現的。事件,就是文檔或瀏覽器窗口發生的一些特定的交互瞬間。可使用監聽器(或事件處理程序)來預約事件,以便事件發生時執行相應的代碼。通俗的說,這種模型其實就是一個觀察者模式。(事件是對象主題,而這一個個的監聽器就是一個個觀察者)
事件流描述的就是從頁面中接收事件的順序。而IE
和Netscape
提出了徹底相反的事件流概念。IE
事件流是事件冒泡,而Netscape
的事件流就是事件捕獲。
打個比喻,若是你把手指放在圓心上(多個同心圓),那麼你的手指指向的不是一個圓,而是紙上全部的圓。若是你單擊了某個按鈕,在單擊按鈕的同時,也單擊了按鈕的容器元素,甚至是整個頁面。html
IE
的事件流叫作事件冒泡。即事件開始時由最具體的元素(文檔中嵌套層次最深的那個節點)接收,而後逐級向上傳播到較爲不具體的節點(文檔)。全部現代瀏覽器都支持事件冒泡,而且會將事件一直冒泡到window
對象。
事件捕獲的思想是不太具體的節點應該更早的接收到事件,而在最具體的節點應該最後接收到事件。事件捕獲的用以在於 事件到達預約目標以前捕獲它。IE9+、Safari、Chrome、Opera
和Firefox
支持,且從window
開始捕獲(儘管DOM2
級事件規範要求從document
)。因爲老版本瀏覽器不支持,因此不多有人使用事件捕獲。
「DOM2級事件」規定事件流包括三個階段,事件捕獲階段、處於目標階段和事件冒泡階段。首先發生的事件捕獲,爲截獲事件提供了機會。而後是實際的目標接收了事件。最後一個階段是冒泡階段,能夠在這個階段對事件作出響應。
以上這段話,就是咱們DOM事件流的根本了,這段話很是重要,雖然看似簡單,可是裏面包含着不少小細節,我會在下面例子中所有講到。前端
如今就開始來說解這個有趣的例子,先把代碼貼上來,大體的結構十分簡單。因爲segmentfault代碼過長時會有滾動條,這裏我把它切分,並省略了一些沒必要要的結點,便於觀看。java
<div id="a"> <div id="b"> <div id="c"></div> </div> </div>
#a{ width: 300px; height: 300px; background: pink; } #b{ width: 200px; height: 200px; background: blue; } #c{ width: 100px; height: 100px; background: yellow; }
var a = document.getElementById("a"), b = document.getElementById("b"), c = document.getElementById("c"); c.addEventListener("click", function (event) { console.log("c1"); // 注意第三個參數沒有傳進 false , 由於默認傳進來的是 false //,表明冒泡階段調用,我的認爲處於目標階段也會調用的 }); c.addEventListener("click", function (event) { console.log("c2"); }, true); b.addEventListener("click", function (event) { console.log("b"); }, true); a.addEventListener("click", function (event) { console.log("a1"); }, true); a.addEventListener("click", function (event) { console.log("a2") }); a.addEventListener("click", function (event) { console.log("a3"); event.stopImmediatePropagation(); }, true); a.addEventListener("click", function (event) { console.log("a4"); }, true);
整個的html頁面就是下面這三個小盒子。segmentfault
那麼如今有三個問題:瀏覽器
a一、a3
) stopImmediatePropagation
包含了stopPropagation
的功能,即阻止事件傳播(捕獲或冒泡),但同時也阻止該元素上後來綁定的事件處理程序被調用,因此不輸出 a4
。由於事件捕獲被攔截了,天然不會觸發 b、c
上的事件,因此不輸出 b、c一、c2
,冒泡更談不上了,因此不輸出 a2
。函數
a一、a二、a3
) 不該該是 a一、a三、a2
嗎?有同窗就會說:「a一、a3
但是在捕獲階段被調用的處理程序的,a2
是在冒泡階段被調用的啊。」這正是要說明的:雖然這三個事件處理程序註冊時指定了true
、false
,但如今事件流是處於目標階段,不是冒泡階段、也不是捕獲階段,事件處理程序被調用的順序是註冊的順序。不論你指定的是true
仍是false
。換句話來講就是如今點擊的是a
這個盒子自己,它處於事件流的目標狀態,而既非冒泡,又非捕獲。(須要注意的是,此時的eventPhase
爲2,說明事件流處於目標階段。當點擊a
的時候,先從document
捕獲,而後一步步往下找,找到a
這個元素的時候,此時的target
和currentTarget
是一致的,因此認定到底了,不須要再捕獲了,此時就按順序執行已經預約的事件處理函數,執行完畢後再繼續往上冒泡...)spa
event.stopImmediatePropagation
,點擊c,會輸出什麼?(答案是 a一、a三、a四、b、c一、c二、a2
) 若是同一個事件處理程序(指針相同,好比用 handler
保存的事件處理程序),用 addEventListener
或 attachEvent
綁定屢次,若是第三個參數是相同的話,也只會被調用一次。固然,若是第三個參數一個設置爲true
,另外一個設置爲false
,那麼會被調用兩次。
而在這裏,都是給監聽函數的回調賦予了一個匿名函數,因此其實每一個處理函數都會被調用。須要注意的是,若是你還不明白爲何在c
上觸發的先是c1
再是c2
的話,那麼你就須要在去看看第二個問題所描述的內容了。設計
以上,就是本文章的內容,文章最後的例子是從網上某個地方看到的,可是具體的出處已經忘記了,若是有同窗找到了出處,煩請告知。
參考資料:《JavaScript高級程序設計》