DOM事件的工作原理

導讀:
本文是teren對DOM事件知識點所做的進一步整理,整理資料主要參考DOM事件簡介饑人谷課件,如果對DOM事件有什么不了解的地方,可以直接參考原文出處。
這篇筆記的目的有兩個,一是作為自己整理的資料方便日后查閱,畢竟自己整理的資料用起來更加得心應手,思路更加契合自己;二是希望通過在撰寫筆記過程中強化記憶;
如果這篇文章有什么能夠幫助到各位讀者,我要萬分感謝原文的作者以及原文的翻譯!!!
最后,這篇文章的整理結構如目錄所示。

目錄
1.預備知識
監聽事件
移除事件
2.事件階段
3.event對象
4.事件的操作
停止事件傳播
阻止瀏覽器的默認行為
自定義事件
代理事件監聽
5.FQA
DOM 0事件和DOM 2事件
事件流的三種模型
IE兼容性


1. 預備知識

監聽事件

在為節點添加事件時,推薦使用addEventListener接口(IE的使用attachEvent接口)

node.addEventListener(eventname,callback[,useCapture])
  • eventname:監聽事件的名稱,如
    click/mouseover/mouseout/mousedown/mouseup/load/unload等事件

  • callback:事件觸發時被調用的函數,此時會自動產生一個event對象,作為第一個參數傳入callback

var ul = document.querySelector('ul')
ul.addEventListener('click',function(event){console.log(event.target)})

關于event對象后面詳細講解

  • useCapture:決定回調函數(callback)是否在“捕獲(capture)”階段被觸發,,默認是false,即在冒泡階段被觸發
    關于事件階段后面詳解,現在給個范例演示,可以看完事件階段章節后再“回調”查看(~ ̄▽ ̄)~
    demo: usecapture
移除事件

移除事件使用removeEventListener接口

node.removeEventListener(eventname,callback)

值的注意的是:
通過addEventListener添加的事件處理程序只能通過removeEventListener移除,移除時參數與添加的時候相同;
這也就意味著:在移除事件時回調函數不能為匿名函數,因為匿名函數雖然方法體一樣,但是句柄(可以理解為函數名)卻不相同
也就是說當初定義回調函數必須以以下形成出現

var ul  = document.querySelector('ul')
//method 1
function printHello(){
  alert('hello world')
}
//method 2
handler = function (){
  alert('hello world 2')
}
ul.addEventListener('click',printHello);
ul.addEventListener('click',handler);
ul.addEventListener('click',function(){alert('hello world 3')})
ul.removeEventListener('click',printHello);
//此時你要移除第3個hello world,那么你無法碼下去
//ul.removeEventListener('click',)

demo : removeEvent

2.事件階段

以一個例子去描述事件階段

  <ul>
    <li id="demo1">demo1</li>
    <li id="demo2">demo2</li>
    <li id="demo3">demo3</li>
  </ul>
var demo2 = document.querySelector('#demo2')
demo2.addEventListener('click',callback)
function callback(){
  console.log('demo')
}

給li#demo2節點添加click事件,在點擊li#demo2節點時,點擊事件不是直接在該節點直接發生,而是分為三個事件階段:

  • click事件從html文檔的根節點window流向目標節點li#demo2(捕獲階段)
  • 然后在目標節點上click事件觸發(目標階段)
  • 最后再返回到文檔的根節點(冒泡階段)


    事件階段

demo:event phases
小結:
事件觸發的整個過程可分為三個階段:

  • 捕獲階段
    事件的第一個階段是捕獲階段。事件從文檔的根節點出發,隨著DOM樹的結構向事件的目標節點流去。途中經過各個層次的DOM節點,并在各節點上觸發捕獲事件,直到到達事件的目標節點。

類似水流一樣,從源頭流向目的地

  • 目標階段
    當事件到達目標節點的,事件就進入了目標階段。事件在目標節點上被觸發,然后會逆向回流,直到傳播至最外層的文檔節點
    【注】
    或許有人會疑問?事件在目標節點被觸發,那么設置usecapture還有什么用處呢?
    我的理解是:
    你為節點設置事件是一回事,你觸發事件時是另一回事;
    節點的事件觸發時點可分為上述三個階段,在乎你怎么設置
    下面代碼中,li#demo2一定是在目標階段被觸發,而ul則在乎你的設置,下例設置為捕獲階段被觸發
var ul = document.querySelector('ul')
var demo2  = document.querySelector('#demo2')
function printUl(){alert('Ul')}
function printList(){
  alert('List')
}
//當點擊li#demo2時,到了目標階段觸發li#demo2的click事件
//當你為ul的usecapture設置true時,意味著ul在捕獲階段觸發click事件
ul.addEventListener('click',printUl,true)
demo2.addEventListener('click',printList)

demo:how to recognize event phases

  • 冒泡階段
    事件在目標元素上觸發后,并不在這個元素上終止。它會隨著DOM樹一層層向上冒泡,直到到達最外層的根節點

3.event對象

event對象是在事件第一次觸發時候被創建,并且一直伴隨著事件在DOM結構中流轉的整個生命周期。
event對象會被作為第一個參數傳遞給事件監聽的回調函數。
event對象中包含大量當前事件相關的信息:

屬性/方法 備注
type 事件名稱
target 事件的目標節點
currentTarget 事件觸發時的當前節點
bubbles 判斷節點的事件是否是在冒泡階段捕獲
preventDefault(function) 阻止瀏覽器中用戶代理對當前事件的相關默認行為被觸發。比如阻止a元素的click事件加載一個新的頁面
cancelable(boolean) 指明這個事件的默認行為是否可以通過調用event.preventDefault來阻止,即只有cancelable為true的時候,調用event.preventDefault才能生效
stopPropagation(function) 阻止當前事件流上后面的元素的回調函數被觸發,當前節點上針對此事件的其他回調函數依然會被觸發
stopImmediatePropagation(function) 阻止當前事件流上后面所有的回調函數被觸發,也包括當前節點上針對此事件已綁定的其他回調函數
eventPhase(number) 表示當前這個事件所處的階段(phase):none(0), capture(1),target(2),bubbling(3)
timeStamp(number) 事件發生的時間

下面將一些簡單的屬性放在下面的示例中,復雜的方法將在事件操作章節單獨羅列
demo : event simple property

4.事件的操作

停止事件傳播

通過調用事件對象的stopPropagation方法,在任何階段(捕獲階段或者冒泡階段)中斷事件的傳播;
此后,事件不會在后面傳播過程中的經過的節點上調用任何的監聽函數;
demo:stopPropagation
但event.stopPropagation()不會阻止當前節點上此事件其他的監聽函數被調用。如果你希望阻止當前節點上的其他回調函數被調用的話,你可以使用更激進的event.stopImmediatePropagation()
方法;
demo:stopImmediatePropagation

阻止瀏覽器的默認行為

當特定事件發生的時候,瀏覽器會有一些默認的行為作為反應。例如,使用a元素時會自動添加click事件,當a元素上click事件觸發時,它會向上冒泡直到DOM結構的最外層document,瀏覽器會解釋href屬性,并且在窗口中加載新地址的內容。
如果我們需要阻止瀏覽器針對點擊事件的默認行為,可以調用event.preventDefault()
demo:preventDefault

自定義事件

【注】
知道有這么一回事,這篇不詳講。

代理事件監聽

所謂代理事件監聽,指的是不直接在監聽的目標節點上添加事件監聽函數,而是通過其他的節點代為監聽目標節點的事件;

舉個例子:
如果有一個列表ul包含了100個子元素li,它們都需要對click事件做出相似的響應,那么我們可能需要查詢這100個子元素,并分別為他們添加上事件監聽器。這樣的話,我們就會產生100個獨立的事件監聽器

代理事件監聽可以讓我們更簡單的處理這種情況。我們不去監聽所有的子元素的click事件,相反,我們監聽他們的父元素ul。當一個li元素被點擊的時候,這個事件會向上冒泡至ul,觸發回調函數。我們可以通過檢查事件的event.target屬性來判斷具體是哪一個li被點擊了。
這樣一來,僅僅使用了一個上層的事件監聽器,并且我們不需要在為添加元素而考慮它的事件監聽問題
demo:事件代理
但是在實際代理事件監聽中,我們往往使用jQuery提供的on()方法去實現事件代理
demo:事件代理-on()方法

5.FQA

DOM 0級事件處理程序和DOM 2級事件處理程序

首先,了解一下DOM的分級。
DOM是HTML與XML的應用編程接口(API),DOM將整個頁面映射為一個由層次節點組成的文件,有1級、2級、3級共3個級別。
1級DOM
1級DOM,由DOM核心與DOM HTML兩個模塊組成。
DOM核心能映射以XML為基礎的文檔結構,允許獲取和操作文檔的任意部分。
DOM HTML通過添加HTML專用的對象與函數對DOM核心進行了擴展。
2級DOM
鑒于1級DOM僅以映射文檔結構為目標,DOM 2級面向更為寬廣。通過對原有DOM的擴展,2級DOM通過對象接口增加了:
DOM視圖:描述跟蹤一個文檔的各種視圖(使用CSS樣式設計文檔前后)的接口;
DOM事件:描述事件接口;
DOM樣式:描述處理基于CSS樣式的接口;
DOM遍歷與范圍:描述遍歷和操作文檔樹的接口;
3級DOM
3級DOM通過引入統一方式載入和保存文檔和文檔驗證方法對DOM進行進一步擴展
"0級"DOM
需要注意的是并沒有標準被稱為0級DOM,它僅是DOM歷史上一個參考點(0級DOM被認為是在Internet Explorer 4.0 與Netscape Navigator4.0支持的最早的DHTML)

也就是說:
DOM 0級事件處理程序是 通過javascript制定事件處理程序的傳統方式,具體實現方式是:

var btn = document.getElementById("btn");            
btn.onclick = function(){                
alert(this.id);//this指定當前元素btn  
}
刪除DOM0事件處理程序,
只要將對應事件屬性置為null即可。btn.onclick = null;

DOM 0級事件處理程序的優點是簡單且具有跨瀏覽器的優勢,缺點是一個事件處理程序只能對應一個處理函數

DOM2級事件處理程序是在2級DOM中規定的API,通過addEventListener(IE為attachEvent)去監聽事件,具體實現方式是:

var btn = document.getElementById("btn");
function handler(){
  alert(this.id)//this指定當前元素btn
}
btn.addEventListener('click',handler)

demo:addEventListener
同時制定了刪除事件處理程序的方法
removeEventListener(IE為detachEvent),關于removeEventListener的注意事項請詳見上文移除事件章節;
至于attachEvent與addEventListener的區別詳見后文IE兼容性
addEventListener的優點是一個事件處理程序能對應多個處理函數,缺點是存在兼容性問題。

事件流的三種模型

所謂事件流,指的是頁面捕獲事件的順序,目前有三種模型:

  • IE的事件冒泡:當發生事件時,目標節點先捕獲,然后逐級向上傳播到父節點,即事件監聽處于冒泡階段

  • Netscape的事件捕獲:當發生事件時,最先觸發父節點的事件監聽函數,然后逐漸向下傳播到目標節點,即事件監聽處于捕獲階段

  • 2級DOM規定事件流包括三個階段,事件捕獲階段,處于目標階段,事件冒泡階段

IE兼容性

IE并不支持addEventListener和removeEventListener方法,而是實現了兩個類似的方法:
attachEvent(eventname,callback)
detachEvent(eventname,callback)
由于IE指支持事件冒泡,所以添加的程序會被添加到冒泡階段。
【注意】
IE的事件監聽的方法與addEventListener方法不同之處包括:
eventname必須包含on以及沒有usecapture;
同時,使用attachEvent方法和addEventListener主要區別在于事件處理程序的作用域。采用addEventListener,事件處理程序會在其所屬元素的作用域內運行。使用attachEvent,事件處理程序會在全局作用域內運行,因此this等于window。

var btn = document.getElementById("btn");
function handler(){
  alert(this.id)//this指定window
}
btn.attachEvent('onclick',handler)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 事件是一種異步編程的實現方式,本質上是程序各個組成部分之間的通信。DOM支持大量的事件,本節介紹DOM的事件編程。...
    周花花啊閱讀 597評論 0 3
  • 聲明:本文來源于http://www.webzsky.com/?p=731我只是在這里作為自己的學習筆記整理一下(...
    angryyan閱讀 7,121評論 1 6
  • 以下文章為轉載,對理解JavaScript中的事件處理機制很有幫助,淺顯易懂,特分享于此。 什么是事件? 事件(E...
    jxyjxy閱讀 3,069評論 1 10
  • 事件綁定的方式 給 DOM 元素綁定事件分為兩大類:在 html 中直接綁定 和 在 JavaScript 中綁定...
    Bruce_zhuan閱讀 1,055評論 0 6
  • 可能自己太愛自己女朋友琪寶寶了,每天不敢接近她,因為我知道自己不夠優秀,我很自卑自己不能讓她炫耀自己男朋友多么厲害...
    維小凱閱讀 302評論 0 0