事件流
定義:
1.事件流
描述的是從頁面中接收事件的順序,也可理解為事件在頁面中傳播的順序。
2.事件
就是使用者或瀏覽器自身執行的某種動作。諸如click(點選)、load(載入)、mouseover(滑鼠懸停)。
3.事件處理程式
響應某個事件的函式就叫事件處理程式(或事件偵聽器)。
下面所示例子註冊事件的方式均使用DOM2級事件定義的事件處理程式進行註冊,相容性的問題不涉及。'DOM2級事件'定義了兩個方法,用於處理指定和刪除事件處理程式的操作:addEventListener()
和removeEventListener()
。所有DOM節點中都包含這兩個方法,並且它們都接收3個引數:要處理的事件名、作為事件處理程式的函式和一個布林值。當這個布林值為true
時,表示在捕獲階段呼叫事件處理程式;若果是false
,表示在冒泡階段呼叫事件處理程式。
事件的作用範圍討論
示例1
html
<div id="wrap">
<div id="outer">
<div id="inner"></div>
</div>
</div>
css
#wrap {
width: 200px;
height: 200px;
background: orange;
}
#outer {
position: relative;
top: 50px;
left: 50px;
width: 100px;
height: 100px;
background: #eeddff;
}
#inner {
position: relative;
top: 25px;
left:25px;
width: 50px;
height: 50px;
background: #44ddff;
}
js
var wrap = document.getElementById('wrap');
wrap.addEventListener('click',function(){
alert('789');
},false);
output
問題1:容器元素wrap註冊了事件,那麼此事件的作用範圍是什麼?
思考1:根據上面例子,當點選橘色塊中(包括被子元素覆蓋的部分)任何一部分時,都會彈出789,點選橘色塊外面的部分並沒有任何反應,那麼我們是不是就可以得出這這樣結論,元素註冊事件的作用範圍為元素自身在頁面中所佔的空間大小,但是真的就是這樣嗎?下面我們做個試驗
試驗1:
css程式碼修改如下,其他部分同上
#wrap {
width: 200px;
height: 200px;
background: orange;
}
#outer {
position: relative;
top: 50px;
left: 50px;
width: 100px;
height: 100px;
background: #eeddff;
}
/*inner中的top被修改*/
#inner {
position: relative;
top: 152px;
left:25px;
width: 50px;
height: 50px;
background: #44ddff;
}
output
結論1:當點選橘色塊外淺藍色部分的時候,同樣的也彈出了789,而淺藍色部分是巢狀在wrap元素之內的元素,故可得出結論,當元素註冊了事件,此事件的作用範圍為:1.元素自己所佔頁面空間部分加巢狀元素所佔空間範圍(若巢狀元素覆蓋在容器元素上,則事件的作用範圍為容器元素自身所佔空間大小)。這意味著,當互動事件觸發時,該元素以及它的所有後代元素都可以接收到該事件。
事件的執行順序討論
問題2:根據上面的示例1
,那麼這裡大家可以再思考一個問題,若容器元素wrap
以及其巢狀元素outer
,inner
都註冊了click事件,根據試驗1
得出的結論,那麼巢狀在最裡層的元素inner
所佔頁面的空間範圍內,一共有3個click事件都作用在其上,那麼當在inner元素的作用範圍內點選頁面時,3個事件的事件處理程式執行的順序又是如何的?
要解決上面我提出的問題2,這就涉及到了兩種處理事件流的不同的機制,事件冒泡和事件捕獲
事件冒泡
IE的事件流叫事件冒泡,即事件開始時由最具體的元素(文件中巢狀層次最深的節點)接收,然後逐級向上傳播到較為不具體的節點。
示例2
將引數設為false,讓元素在冒泡階段呼叫事件處理程式
css,html程式碼同示例1
js
var wrap = document.getElementById('wrap');
var outer = document.getElementById('outer');
var inner = document.getElementById('inner');
wrap.addEventListener('click',function(){
alert('789');
},false);
outer.addEventListener('click',function(){
alert('456');
},false);
inner.addEventListener('click',function(){
alert('123');
},false);
結論2:在冒泡階段呼叫事件處理程式,上面問題的結果是這樣的:當點選頁面中心淺藍色的部分時,先是彈出123,接著彈出456,最後彈出789。因此當容器元素及其巢狀元素都在冒泡階段
呼叫事件處理程式時:事件按事件冒泡的順序執行事件處理程式。這意味著,當互動事件觸發時,巢狀在最裡層的元素最先接收到該事件,最外層的元素最後接收到該事件。
事件捕獲
Netscape團隊提出的另一種事件流叫事件捕獲,事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件。
示例3
將引數設為true,讓元素在捕獲階段呼叫事件處理程式
css,html程式碼同示例1
js
var wrap = document.getElementById('wrap');
var outer = document.getElementById('outer');
var inner = document.getElementById('inner');
wrap.addEventListener('click',function(){
alert('wrap');
},true);
outer.addEventListener('click',function(){
alert('outer');
},true);
inner.addEventListener('click',function(){
alert('inner');
},true);
結論3:在捕獲階段呼叫事件處理程式,上面問題的結果是這樣的:當點選頁面中心淺藍色的部分時,先是彈出wrap,接著彈出outer,最後彈出inner。因此當容器元素及其巢狀元素都在捕獲階段
呼叫事件處理程式時:事件按事件捕獲的順序執行事件處理程式。這意味著,當互動事件觸發時,巢狀在最外層的元素最先接收到該事件,最裡層的元素最後接收到該事件
問題3:根據思考1,思考2得出的結果,接著又有一個問題我認為需要思考,當同一個元素即在冒泡階段註冊了事件,又在捕獲階段註冊了同一事件,那麼當事件被觸發時,事件的執行順序又會是如何的?
要解決上面我提出的問題3,這就涉及到了DOM事件流
DOM事件流
“DOM2級事件”規定的事件流包括三個階段:事件捕獲階段==>處於目標階段==>事件冒泡階段。首先發生的是事件捕獲階段,為截獲事件提供了機會。然後是實際的目標接收事件。最後一個階段是冒泡階段,以下圖片來自w3c
示例4
css,html程式碼同示例1
js
var wrap = document.getElementById('wrap');
var outet = document.getElementById('outer');
var inner = document.getElementById('inner');
wrap.addEventListener('click',function(){
alert('789');
},false);
outer.addEventListener('click',function(){
alert('456');
},false);
inner.addEventListener('click',function(){
alert('123');
},false);
wrap.addEventListener('click',function(){
alert('wrap');
},true);
outer.addEventListener('click',function(){
alert('outer');
},true);
inner.addEventListener('click',function(){
alert('inner');
},true);
結論4:當點選頁面中心淺藍色部分的時候,先從最不具體的節點捕獲事件,先彈出wrap,接著彈出outer。接著處於目標階段,先彈出123,再彈出inner。緊接著,事件處於冒泡階段,先彈出456,再彈出789。因此我們可以得出結論,當容器元素及巢狀元素,即在捕獲階段
又在冒泡階段
呼叫事件處理程式時:事件按DOM事件流的順序執行事件處理程式,且當事件處於目標階段時,事件呼叫順序決定於繫結事件的書寫順序,按上面的例子為,先呼叫冒泡階段的事件處理程式,再呼叫捕獲階段的事件處理程式。具體demo可看評論,@ Levon