當瀏覽器開發到第四代的時候,瀏覽器的開發團隊遇到了一個很有意思的問題:頁面的哪一部分會擁有特定的事件?要明白這個問題問的是什麼,我們可以想下面的一個問題,加入我們在一張紙上畫了一組同心圓.如果把手放在紙上,那麼手指指向的不是那一個特定的圓而是紙上所有的圓.兩家瀏覽器開發團隊在看待瀏覽器事件方面還是一致的.如果我們單擊 某個按鈕,他們都認爲單擊事件並不僅僅發上在按鈕身上.換句話說,你也同時單擊了按鈕的容器元素,甚至也單擊了整個頁面.
事件流描述的就是從頁面中接收事件的順序.但是IE
和 Netspace
開發團隊提出了差不多但是完全相反的事件流的概念.
IE
的事件流是事件冒泡流Netspace
的事件流是事件捕獲流
1.事件冒泡
IE
的事件流叫做事件冒泡流,即事件開始由最具體的元素(文檔中嵌套層次最深的那個節點)接收,然後逐級向上傳播到不太具體的節點(文檔)
事件默認是冒泡的,子元素觸發事件時,會沿着DOM結構一層一層向上傳遞,這個過程稱爲事件冒泡。
注意:子元素被定位到了其他位置(視覺上脫離了父元素),也會見事件一層一層向上傳遞
看下面的例子:
在這裏綁定事件用的是 DOM2級事件
,它接收三個參數:
- 要處理的事件名
- 作爲事件處理程序的函數
- 布爾值(true,表示在捕獲階段調用事件處理程序,false:表示在事件冒泡階段調用事件處理程序)
<style>
* {
margin:0;
padding:0;
}
#orange{
position: relative;
width: 300px;
height: 300px;
margin:100px auto;
background-color: orange;
}
#purple {
position: absolute;
top:0;
left:0;
right:0;
bottom:0;
margin:auto;
width: 180px;
height: 180px;
background-color: purple;
}
#blue{
position: absolute;
top:0;
left:0;
right:0;
bottom:0;
margin:auto;
width: 80px;
height: 80px;
background-color: #58a;
}
</style>
</head>
<body>
<div id="orange">
<div id="purple">
<div id="blue"></div>
</div>
</div>
<script>
document.getElementById('orange').addEventListener('click', function(){
console.log("我是橘色盒子");
}, false);
document.getElementById('purple').addEventListener('click', function(){
console.log("我是紫色盒子");
}, false);
document.getElementById('blue').addEventListener('click', ()=>{
console.log("我是藍色盒子");
}, false);
</script>
在頁面中的顯示如下圖:
當我點擊最中心的藍色盒子時會打印的信息如下:
當我單擊了頁面中最中心的藍色盒子時,click 事件會按照下面的順序進行傳播:
- div#blue
- div#purple
- div#orange
- body
- html
- doucument
也就是說 click
事件首先在最裏面的藍色盒子上發生,這個元素就是我們開始時單擊的元素,然後 click
事件會沿着 DOM
樹向上傳播,直至傳播到 document
對象:
如下圖所示:
所有的現代瀏覽器都支持事件冒泡,但在具體的實現上還是有一些差別,IE5.5
及更早的版本中事件冒泡會跳過<html>
元素(直接從 body
到 document
).IE9
FireFox
Chrome
和 Safari
則將事件一直冒泡到 window
對象.
2. 事件捕獲
Netspace
團隊提出的另一種事件流叫做 事件捕獲,事件捕獲的思想是不太具體的節點應該更早的接收到事件,而最具體的節點應該最後接收到事件
事件默認是冒泡的,可通過 addEventListener 的第三個參數這隻爲 true 將事件設置爲捕獲,同時,添加了捕獲的事件在使用 removeEventListener 移除時也需要寫第三個參數 true
將上題中的代碼事件監聽中的第三個參數由 false
改爲 true
,觀察一下結果:
document.getElementById('orange').addEventListener('click', function(){
console.log("我是橘色盒子");
}, true);
document.getElementById('purple').addEventListener('click', function(){
console.log("我是紫色盒子");
}, true);
document.getElementById('blue').addEventListener('click', ()=>{
console.log("我是藍色盒子");
}, true);
點擊盒子後打印的信息如下:
我們單擊了頁面中的藍色盒子,那麼元素就會以下列順序觸發 click
事件
- document
- html
- body
- div#orange
- div#purple
- div#blue
在事件捕獲過程中,document
對象首先接受到 click
事件,然後事件沿 dom樹
依次向下,一直傳播到事件的實際目標,即 div#blue
元素,下圖就展示了事件捕獲的過程:
3.DOM 事件流
DOM2級事件
規定的事件流包括三個階段:
- 事件捕獲階段
- 處於目標階段
- 事件冒泡階段
首先發生的是事件捕獲,爲截獲事件提供了機會.然後是實際的目標接受到事件,最後一個階段是事件冒泡階段,這個階段可以對事件作出相應,以上面的案例爲例,點擊最中間的div#blue
盒子會按下圖所示觸發事件:
在DOM
事件流中,實際的目標元素(div#blue
)在捕獲階段不會接收到事件.這意味着在捕獲階段事件從哪個window
到document
再到html
到body
一直到div#purple
就停止了,下一個階段是 “處於目標階段” ,於是在事件div#blue
上發生,然後冒泡階段發生,事件傳回文檔.
4.阻止默認事件和事件冒泡
默認事件 瀏覽器中很多操作都有默認的行爲,比如右鍵打開菜單,我們稱之爲默認事件。
我們可以通過 DOM
中的事件對象來阻止默認事件.
阻止默認事件:
- ev.stopDefault()
- return false
event
對象包含與創建它的特定事件有關的屬性和方法,常見的如下:
阻止事件冒泡等.
屬性/方法 | 類型 | 說明 |
---|---|---|
preventDefault | Function | 取消事件的默認行爲. |
stopImmediate | Propagation() Function | 取消事件的進一步捕獲或冒泡,同時阻止任何事件處理程序被調用 |
stopPropagation | Function | 取消事件的進一步捕獲或冒泡. |
將最開始的案例阻止事件冒泡,改寫如下代碼:
document.getElementById('blue').addEventListener('click', (ev) => {
console.log("我是藍色盒子");
}, false);
改爲:
document.getElementById('blue').addEventListener('click', (ev) => {
ev.stopPropagation();
console.log("我是藍色盒子");
}, false);
點擊藍色盒子後觀察打印結果:
已經成功阻止事件冒泡.
在平時寫的時候,我通常會將默認事件和事件冒泡都阻止,防止產生不必要的麻煩.
ev.stopPropagation();
ev.preventDefault();