導讀:
本文是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',)
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)