javascript:深入理解事件流

阿花和貓發表於2015-08-27

事件流

定義
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

clipboard.png

問題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

clipboard.png

結論1:當點選橘色塊外淺藍色部分的時候,同樣的也彈出了789,而淺藍色部分是巢狀在wrap元素之內的元素,故可得出結論,當元素註冊了事件,此事件的作用範圍為:1.元素自己所佔頁面空間部分加巢狀元素所佔空間範圍(若巢狀元素覆蓋在容器元素上,則事件的作用範圍為容器元素自身所佔空間大小)。這意味著,當互動事件觸發時,該元素以及它的所有後代元素都可以接收到該事件。

事件的執行順序討論

問題2:根據上面的示例1,那麼這裡大家可以再思考一個問題,若容器元素wrap以及其巢狀元素outerinner都註冊了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

eventflow.png

示例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

相關文章