(一)DOM事件流
① 定義
DOM事件流包括
三個階段
:
- 捕獲階段
- 目標階段
- 冒泡階段
首先發生的是事件捕獲,爲截獲事件提供了機會。然後是實際的目標接收到事件。最後一個是冒泡階段,在這個階段對事件做出響應
事件發生時會在元素節點之間按照特定的順序傳播,這個傳播的過程就是DOM事件流
簡單的說:事件的傳播過程即DOM事件流
② 圖解
③ 歷史
當瀏覽器發展到第四代時(IE4及Netscape Communicator4),瀏覽器開發團隊遇到了一個很有意思的問題:頁面的哪一部分會擁有某個特定的事件?要明白這個問題問的是什麼,可以想象畫在一張紙上的一組同心圓。如果你把手指放在圓心上,那麼你的手指指向的不是一個圓,而是紙上所有的圓,兩家公司的瀏覽器開發團隊在看待瀏覽器事件方面還是一致的。如果你單擊了某個按鈕,他們都認爲單擊事件不僅僅發生在按鈕上。換句話說,在單機按鈕的同時,你也單擊了按鈕的容器元素,甚至也單擊了整個頁面。
事件流描述的是從頁面中接受事件的順序。但有意思的是,IE 和 Netscape 開發團隊居然提出了差不多完全相反的概念,IE的事件流是冒泡流,而Netscape Communicator的事件流是事件捕獲流。
(摘自《JavaScript高級程序設計》)
下面我用實例展現一下冒泡和捕獲的不同之處。
(二)冒泡
我給分別給div、section、body、html、window設置了點擊事件,然後只點擊了div,現在我們來分析一下此時的事件傳播過程
首先,進入捕獲階段,實際的目標元素(div)在捕獲階段不會接收到事件。這意味着在捕獲階段,事件從window → html → body → section
就停止了。下一個階段是目標階段,於是事件在div上發生,並在事件處理中被看成冒泡的一部分。然後事件冒泡發生,事件又傳播回window。所以我們看到了下述的事件執行順序。
事件的執行順序是:
小 → 大
div → section → body → html → window
下面貼上代碼(css部分就不貼了,問題不大)
html部分
<body>
<p>body</p>
<section>
<p>父盒子</p>
<div>子盒子</div>
</section>
</body>
JS部分
<script>
var oSection = document.querySelector("section");
var oDiv = document.querySelector("div");
var oHtml = document.documentElement;
var oBody = document.body;
oDiv.addEventListener("click",function(){
alert("我是子盒子")
})
oSection.addEventListener("click",function(){
alert("我是父盒子")
})
oHtml.addEventListener("click",function(){
alert("我是html")
})
oBody.addEventListener("click",function(){
alert("我是body")
})
window.addEventListener("click",function(){
alert("我是window")
})
</script>
(三)捕獲
事件捕獲如何觸發呢?
這時需要用到 addEventListener( )方法的第三個參數(默認爲false,即冒泡階段),將第三個參數改爲true,表示將此事件改爲捕獲階段。
( 如果不瞭解addEventListener( )方法的可以 查看文檔)
接下來我們看一下事件在捕獲階段的觸發順序:
根據上面的代碼,我們先把父盒子section的點擊事件變爲捕獲階段
oSection.addEventListener("click",function(){
alert("我是父盒子")
},true)
看看效果:
首先,進入捕獲階段,實際的目標元素(div)在捕獲階段不會接收到事件。但我們把section的點擊事件變爲了捕獲階段發生,section的點擊事件被捕獲了,所以最先觸發。 下一個階段是目標階段,於是事件在div上發生,並在事件處理中被看成冒泡的一部分。然後事件冒泡發生,事件又傳播回window。所以我們纔看到了下述的事件執行順序。
此時的執行順序是:
section → div → body → html → window
現在,我們將所有的事件都變成捕獲階段,看看效果:
此時的執行順序是:
window → html → body → section → div
與冒泡階段的執行順序完全相反。
(四)在不同的階段執行事件
多數支持DOM事件流的瀏覽器都實現了一種特定的行爲:即使 “
DOM2級事件
” 規範明確要求捕獲階段不會涉及事件目標,但IE9,Safari,Chrome,Firefox 和 Opera9.5 以及更高版本都會在事件捕獲階段觸發事件對象上的事件。結果就是有兩個機會在目標對象上操作事件。
(摘自《JavaScript高級程序設計》)
想要了解HTML,DOM0,DOM2事件處理函數可以查看此文章
下面通過實例實踐一下:
首先,讓所有的事件在冒泡階段才觸發,然後給section綁定兩個點擊事件,一個在捕獲階段,一個在冒泡階段
oSection.addEventListener("click",function(){
alert("我是父盒子,我在捕獲階段被觸發了")
},true)//捕獲階段
oSection.addEventListener("click",function(){
alert("我是父盒子,我在冒泡階段被觸發了")
})
看看效果:
可以看到,section的兩個點擊事件都觸發了,一個在捕獲階段,一個在冒泡階段。
(五)提高內存和性能 – 事件委託
對 “事件處理程序過多” 問題的解決方案就是事件委託。事件委託
利用了事件冒泡
,只指定一個事件處理程序,就可以管理某一項類型的所有事件。例如,click事件會一直冒泡到window層次。也就是說,我們可以爲整個頁面指定一個onclick事件,而不必給每個可單擊的元素分別添加事件
(事件處理程序指響應某個事件的函數)
下面看看效果和代碼:
① 實例
JS代碼
<script>
window.addEventListener("click", function (e) {
switch (e.target.className) {//我事先給每個元素設定了一個專屬class
case "html":
alert("我是html");
break;
case "body":
alert("我是body");
break;
case "section":
alert("我是父盒子");
break;
case "div":
alert("我是子盒子");
break;
}
})
</script>
上述代碼中,我們使用事件委託只爲window添加了一個onclick事件處理函數,由於所有的元素都是window的子節點,而且它們的事件會冒泡,所以單擊事件最終會被這個函數處理,然後通過檢測屬性的className來檢測是哪個元素被點擊,從而做出不同的動作。
上述代碼中還用到了event事件對象,target返回觸發此事件的元素。也就是說,誰觸發了window的點擊事件,target就是誰。
② 優點
上述代碼與前面未使用事件委託的代碼比一比,會發現這段代碼的事前消耗更低,因爲不需要取得DOM元素(或者只需要取得一個DOM元素),只添加了一個事件處理程序。雖然對於用戶來說最終的結果相同,但是這種技術需要佔用的內存更少。所有用到按鈕的事件(多鼠標事件和鍵盤事件)都適合採用事件委託技術。
這樣做與採取傳統的做法相比具有如下優點:
- window對象
很快就可以訪問
,只要可單擊的元素呈現在頁面上,就可以立即具備適當的功能 - 在頁面中設置事件處理程序所需的事件更少。只添加一個事件處理程序
所需的DOM引用更少,所花的時間也更少
- 整個頁面
佔據的內存空間更少
,能夠提升整體性能。
我的與此文章相關的文章:
1.DOM事件處理函數、DOM0,DOM2的優缺點及IE兼容
結語:若文章有錯誤的地方,煩請在評論區指出。當然,我會不定時的重新編輯寫過的文章,查錯及優化,希望能將最好的文章展現給讀者。