何為 DOM 事件,HTML DOM 使JavaScript 有能力對 HTML 事件做出反應(yīng)。(例如,點擊 DOM 元素,鍵盤被按,輸入框輸入內(nèi)容以及頁面加載完畢等)
一個 DOM 事件可以分為捕獲過程
、觸發(fā)過程
、冒泡過程
。 DOM 事件流為 DOM 事件的處理及執(zhí)行的過程。下面以一個<a>
元素被點擊為例。
關(guān)于捕獲過程的補充
如果有一個支持三個階段的事件,它一定在觸發(fā)時遵循下面的順序:
Capture -> Target -> Bubbling
使用下面的代碼來舉例:
// 添加Capture階段事件
docuemnt.addEventListener('click',function(){
alert('capture:'+1);
},true);
tableNode.addEventListener('click',function(){
alert('capture:'+2);
},true);
tdNode.addEventListener('click',function(){
alert('capture:'+3);
},true);
// 添加Bubbling階段事件
docuemnt.addEventListener('click',function(){
alert('bubble:'+1);
});
tableNode.addEventListener('click',function(){
alert('bubble:'+2);
});
tdNode.addEventListener('click',function(){
alert('bubble:'+3);
});
輸出結(jié)果為:
capture:1
capture:2
capture:3
bubble:3
bubble:2
bubble:1
// 對document添加了三個bubbling階段的事件
document.addEventListener('click',function(){
alert(1);
});
document.addEventListener('click',function(){
alert(2);
});
document.addEventListener('click',function(){
alert(3);
});
如上面的代碼所示,其為同一節(jié)點添加了同一階段的多個事件,那執(zhí)行順序如何呢? 早期并沒有規(guī)范定義,DOM 3 中規(guī)范已經(jīng)明確規(guī)定 同一節(jié)點同一階段的事件應(yīng)按照注冊函數(shù)的順序執(zhí)行。
在實際項目過程中,某些情況下比如若干的組件或者模塊都需要監(jiān)聽某個節(jié)點的某個事件,但是組件或者模塊的生成(即添加事件的時機)是不一定保證順序的,所以這個情況下如果某個組件對這個節(jié)點的這個事件的優(yōu)先級特別高(需要保證必須先觸發(fā)這個組件里的這個事件)而這個平臺又支持這個階段事件的話可以添加 capture 階段事件,用三階段的順序來保證,比如移動平臺模擬手勢的實現(xiàn)會添加 document 上 touchXXX 的 capture 階段事件,以優(yōu)先識別手勢操作 當(dāng)然實踐過程中考慮到不同瀏覽器對三階段支持的情況的差異,大部分情況下都采用的是 bubbling 階段的事件
—— 蔡劍飛 網(wǎng)易前端工程師
NOTE:低版本 IE 中并未實現(xiàn)捕獲過程。也不是所有事件均存在這三個完整的過程(例如 load
沒有冒泡事件)
NOTE+:在這三個階段中無論將事件捕獲和事件處理注冊到任意一個父或祖父節(jié)點上都會被觸發(fā)事件。
事件注冊,取消以及觸發(fā)其作用對象均為一個 DOM 元素。
eventTarget.addEventListener(type, listener[,useCapture])
NOTE:useCapture
為設(shè)定是否為捕獲過程,默認(rèn)事件均為冒泡過程,只有 useCapture
為 true
時才會啟用捕獲過程。
// 獲取元素
var elem = document.getElemenyById('id');
// 事件處理函數(shù)
var clickHandler = function(event) {
// statements
};
// 注冊事件
elem.addEventListener('click', clickHandler, false);
// 第二種方式,不建議使用
elem.onclick = clickHandler;
// 或者來彌補只可觸發(fā)一個處理函數(shù)的缺陷
elem.onclick = function(){
clickHandler();
func();
// 其他處理函數(shù)
};
eventTarget.removeEventListener(type, listener[,useCapture]);
// 獲取元素
var elem = document.getElemenyById('id');
// 取消事件
elem.removeEventListener('click', clickHandler, false);
// 第二種方式。不建議使用
elem.onclick = null;
點擊元素,按下按鍵均會觸發(fā) DOM 事件,當(dāng)然也可以以通過代碼來觸發(fā)事件。
eventTarget.dispatchEvent(type);
// 獲取元素
var elem = document.getElemenyById('id');
// 觸發(fā)事件
elem.dispatchEvent('click');
以上均為 W3C定義的標(biāo)準(zhǔn)定義,但早期瀏覽器 IE8 及其以下版本,均沒有采用標(biāo)準(zhǔn)的實現(xiàn)方式。不過這些低版本瀏覽器也提供了對于 DOM 事件的注冊、取消以及觸發(fā)的實現(xiàn)。
事件注冊與取消,attchEvent/detachEvent
。事件觸發(fā),fireEvent(e)
,其也不存在捕獲階段(Capture Phase)。
注冊事件
var addEvent = document.addEventListener ?
function(elem, type, listener, useCapture) {
elem.addEventListener(type, listener, useCapture);
} :
function(elem, type, listener, useCapture) {
elem.attachEvent('on' + type, listener);
}
取消事件
var addEvent = document.removeElementListener ?
function(elem, type, listener, useCapture) {
elem.removeElementListener(type, listener, useCapture);
} :
function(elem, type, listener, useCapture) {
elem.detachEvent('on' + type, listener);
}
調(diào)用事件處理函數(shù)時傳入的信息對象,這個對象中含有關(guān)于這個事件的詳細(xì)狀態(tài)和信息,它就是事件對象 event
。其中可能包含鼠標(biāo)的位置,鍵盤信息等。
// 獲取元素
var elem = document.getElemenyById('id');
// 事件處理函數(shù)
var clickHandler = function(event) {
// statements
};
// 注冊事件
elem.addEventListener('click', clickHandler, false);
NOTE:在低版本 IE 中事件對象是被注冊在 window
之上而非目標(biāo)對象上。使用下面的兼容代碼既可解決。
var elem = document.getElemenyById('id');
// 事件處理函數(shù)
var clickHandler = function(event) {
event = event || window.event;
// statements
};
屬性
方法
event.stopPropagation()
(W3C規(guī)范方法),如果在當(dāng)前節(jié)點已經(jīng)處理了事件,則可以阻止事件被冒泡傳播至 DOM 樹最頂端即 window
對象。
event.stopImmediatePropagation()
此方法同上面的方法類似,除了阻止將事件冒泡傳播值最高的 DOM 元素外,還會阻止在此事件后的事件的觸發(fā)。
event.cancelBubble=true
為 IE 低版本中中對于阻止冒泡傳播的實現(xiàn)。
默認(rèn)行為是指瀏覽器定義的默認(rèn)行為(點擊一個鏈接的時候,鏈接默認(rèn)就會打開。當(dāng)我們雙擊文字的時候,文字就會被選中),比如單擊鏈接可以打開新窗口。
Event.preventDefault()
為 W3C 規(guī)范方法,在 IE 中的實現(xiàn)方法為 Event.returnValue=false
。
事件類型 | 是否冒泡 | 元素 | 默認(rèn)事件 | 元素例子 |
---|---|---|---|---|
load | NO | Window, Document, Element | None | window, image, iframe |
unload | NO | Window, Document, Element | None | window |
error | NO | Window, Element | None | window, image |
select | NO | Element | None | input, textarea |
abort | NO | Window, Element | None | window, image |
在目標(biāo)圖標(biāo)不能正常載入時,載入備份替代圖來提供用戶體驗。
<img src="http://sample.com/img.png" rel="external nofollow" onerror="this.src='http://sample.com/default.png'">
事件類型 | 是否冒泡 | 元素 | 默認(rèn)事件 | 元素例子 |
---|---|---|---|---|
resize | NO | Window, Element | None | window, iframe |
scroll | NO/YES | Document, Element | None | document, div |
NOTE:resize
為改變?yōu)g覽器或iframe
的窗體大小時會觸發(fā)事件,scroll
則會在滑動內(nèi)容時觸發(fā),作用于 Document
則不會冒泡,作用于內(nèi)部元素則會冒泡。
DOM 事件中最常見的事件之一。
事件類型 | 是否冒泡 | 元素 | 默認(rèn)事件 | 元素例子 |
---|---|---|---|---|
click | YES | Element | focus/activation | div |
dbclick | YES | Element | focus/activation/select | div |
mousedown | YES | Element | drag/scroll/text selection | div |
mosuemove | YES | Element | None | div |
mouseout | YES | Element | None | div |
mouseover | YES | Element | None | div |
mouseup | YES | Element | context menu | div |
mouseenter | NO | Element | None | div |
mouseleave | NO | Element | None | div |
NOTE:mouseenter
與 mouseover
的區(qū)別為前者在鼠標(biāo)在子元素直接移動不會觸發(fā)事件,而后者會觸發(fā)。mouseleave
與 mouseout
同上相似。
鼠標(biāo)的移動過程中會產(chǎn)生很多事件。事件的監(jiān)察頻率又瀏覽器決定。
例子:從元素 A 上方移動過
mousemove -> mouseover(A) -> mouseenter(A) -> mousemove(A) -> mouseout(A) -> mouseleave(A)
例子:點擊元素
mousedown -> [mousemove] -> mouseup -> click
<div id="div0"></div>
<style media="screen">
#div0 {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
border: 1px solid black;
}
</style>
var elem = document.getElemenyById('div0');
var clientX, clientY, isMoving;
var mouseDownHandler = function(event) {
event = event || window.event;
clientX = event.clientX;
clientY = event.clientY;
isMoving = true;
}
var mouseMoveHandler = function(event) {
if (!isMoving) return;
event = event || window.event;
var newClientX = event.clientX,
newClientY = event.clientY;
var left = parseInt(elem.style.left) || 0,
top = parseInt(elem.style.top) || 0;
elem.style.left = left + (newClientX - clientX) + 'px';
elem.style.top = top + (newClientY - clientY) + 'px';
clientX = newClientX;
clientY = newClientY;
}
var mouseUpHandler = function() {
isMoving = false;
}
addEvent(elem, 'mousedown', mouseDownHandler);
addEvent(elem, 'mouseup', mouseUpHandler);
addEvent(elem, 'mousemove', mouseMoveHandler);
事件類型 | 是否冒泡 | 元素 | 默認(rèn)事件 | 元素例子 |
---|---|---|---|---|
wheel | YES | Element | scroll or zoom document | div |
屬性
其用于處理元素獲得或失去焦點的事件。(例如輸入框的可輸入狀態(tài)則為獲得焦點,點擊外部則失去焦點)
事件類型 | 是否冒泡 | 元素 | 默認(rèn)事件 | 元素例子 |
---|---|---|---|---|
blur | NO | Window, Element | None | window, input |
focus | NO | Window, Element | None | window, input |
focusin | NO | window, Element | None | window, input |
focusout | NO | window, Element | None | window, input |
NOTE:blur
失去焦點時,focus
獲得焦點時,focusin
即將獲得焦點,focusout
即將失去焦點。
屬性
一個元素失去,既另一個元素獲得焦點。這里的 relatedTarget
則為相對的那個元素。
輸入框輸入內(nèi)容則會觸發(fā)輸入事件。
事件類型 | 是否冒泡 | 元素 | 默認(rèn)事件 | 元素例子 |
---|---|---|---|---|
beforeInput | YES | Element | update DOM Element | input |
input | YES | Element | None | input |
NOTE:beforeInput
為在按鍵按下后即將將輸入字符顯示之前生成的事件。
NOTE+:IE 并沒有 InputEvent
則需使用 onpropertychange(IE)
來代替。
其用于處理鍵盤事件。
事件類型 | 是否冒泡 | 元素 | 默認(rèn)事件 | 元素例子 |
---|---|---|---|---|
keydown | YES | Element | beforeInput/input/focus/blur/activation | div, input |
keyup | YES | Element | None | div, input |
屬性
事件代理是指在父節(jié)點上(可為元素最近的父節(jié)點也可為上層的其他節(jié)點)處理子元素上觸發(fā)的事件,其原理是通過事件流機制而完成的。可以通過事件對象中獲取到觸發(fā)事件的對象(如下所示)。
var elem = document.getElemenyById('id');
elem.addEventListener('click', function(event) {
var e = event || window.event;
var target = e.target || e.srcElement;
// statements
});
優(yōu)點
缺點
更多建議: