何為 DOM 事件,HTML DOM 使JavaScript 有能力對(duì) HTML 事件做出反應(yīng)。(例如,點(diǎn)擊 DOM 元素,鍵盤被按,輸入框輸入內(nèi)容以及頁(yè)面加載完畢等)
一個(gè) DOM 事件可以分為捕獲過程、觸發(fā)過程、冒泡過程。
DOM 事件流為 DOM 事件的處理及執(zhí)行的過程。下面以一個(gè)<a>元素被點(diǎn)擊為例。
http://wiki.jikexueyuan.com/project/fend_note/images/E/event_flow.jpg" alt="" />
關(guān)于捕獲過程的補(bǔ)充
如果有一個(gè)支持三個(gè)階段的事件,它一定在觸發(fā)時(shí)遵循下面的順序:
Capture -> Target -> Bubbling
http://wiki.jikexueyuan.com/project/fend_note/images/E/event-phases.png" alt="" />
使用下面的代碼來舉例:
// 添加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
http://wiki.jikexueyuan.com/project/fend_note/images/E/event-apply.png" alt="" />
// 對(duì)document添加了三個(gè)bubbling階段的事件
document.addEventListener('click',function(){
alert(1);
});
document.addEventListener('click',function(){
alert(2);
});
document.addEventListener('click',function(){
alert(3);
});
如上面的代碼所示,其為同一節(jié)點(diǎn)添加了同一階段的多個(gè)事件,那執(zhí)行順序如何呢? 早期并沒有規(guī)范定義,DOM 3 中規(guī)范已經(jīng)明確規(guī)定 同一節(jié)點(diǎn)同一階段的事件應(yīng)按照注冊(cè)函數(shù)的順序執(zhí)行。
在實(shí)際項(xiàng)目過程中,某些情況下比如若干的組件或者模塊都需要監(jiān)聽某個(gè)節(jié)點(diǎn)的某個(gè)事件,但是組件或者模塊的生成(即添加事件的時(shí)機(jī))是不一定保證順序的,所以這個(gè)情況下如果某個(gè)組件對(duì)這個(gè)節(jié)點(diǎn)的這個(gè)事件的優(yōu)先級(jí)特別高(需要保證必須先觸發(fā)這個(gè)組件里的這個(gè)事件)而這個(gè)平臺(tái)又支持這個(gè)階段事件的話可以添加 capture 階段事件,用三階段的順序來保證,比如移動(dòng)平臺(tái)模擬手勢(shì)的實(shí)現(xiàn)會(huì)添加 document 上 touchXXX 的 capture 階段事件,以優(yōu)先識(shí)別手勢(shì)操作 當(dāng)然實(shí)踐過程中考慮到不同瀏覽器對(duì)三階段支持的情況的差異,大部分情況下都采用的是 bubbling 階段的事件
—— 蔡劍飛 網(wǎng)易前端工程師
NOTE:低版本 IE 中并未實(shí)現(xiàn)捕獲過程。也不是所有事件均存在這三個(gè)完整的過程(例如 load 沒有冒泡事件)
NOTE+:在這三個(gè)階段中無(wú)論將事件捕獲和事件處理注冊(cè)到任意一個(gè)父或祖父節(jié)點(diǎn)上都會(huì)被觸發(fā)事件。
事件注冊(cè),取消以及觸發(fā)其作用對(duì)象均為一個(gè) DOM 元素。
eventTarget.addEventListener(type, listener[,useCapture])
NOTE:useCapture 為設(shè)定是否為捕獲過程,默認(rèn)事件均為冒泡過程,只有 useCapture 為 true 時(shí)才會(huì)啟用捕獲過程。
// 獲取元素
var elem = document.getElemenyById('id');
// 事件處理函數(shù)
var clickHandler = function(event) {
// statements
};
// 注冊(cè)事件
elem.addEventListener('click', clickHandler, false);
// 第二種方式,不建議使用
elem.onclick = clickHandler;
// 或者來彌補(bǔ)只可觸發(fā)一個(gè)處理函數(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;
點(diǎn)擊元素,按下按鍵均會(huì)觸發(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)的實(shí)現(xiàn)方式。不過這些低版本瀏覽器也提供了對(duì)于 DOM 事件的注冊(cè)、取消以及觸發(fā)的實(shí)現(xiàn)。
事件注冊(cè)與取消,attchEvent/detachEvent。事件觸發(fā),fireEvent(e),其也不存在捕獲階段(Capture Phase)。
注冊(cè)事件
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ù)時(shí)傳入的信息對(duì)象,這個(gè)對(duì)象中含有關(guān)于這個(gè)事件的詳細(xì)狀態(tài)和信息,它就是事件對(duì)象 event。其中可能包含鼠標(biāo)的位置,鍵盤信息等。
// 獲取元素
var elem = document.getElemenyById('id');
// 事件處理函數(shù)
var clickHandler = function(event) {
// statements
};
// 注冊(cè)事件
elem.addEventListener('click', clickHandler, false);
NOTE:在低版本 IE 中事件對(duì)象是被注冊(cè)在 window 之上而非目標(biāo)對(duì)象上。使用下面的兼容代碼既可解決。
var elem = document.getElemenyById('id');
// 事件處理函數(shù)
var clickHandler = function(event) {
event = event || window.event;
// statements
};
屬性
方法
event.stopPropagation()(W3C規(guī)范方法),如果在當(dāng)前節(jié)點(diǎn)已經(jīng)處理了事件,則可以阻止事件被冒泡傳播至 DOM 樹最頂端即 window 對(duì)象。
event.stopImmediatePropagation() 此方法同上面的方法類似,除了阻止將事件冒泡傳播值最高的 DOM 元素外,還會(huì)阻止在此事件后的事件的觸發(fā)。
event.cancelBubble=true 為 IE 低版本中中對(duì)于阻止冒泡傳播的實(shí)現(xiàn)。
默認(rèn)行為是指瀏覽器定義的默認(rèn)行為(點(diǎn)擊一個(gè)鏈接的時(shí)候,鏈接默認(rèn)就會(huì)打開。當(dāng)我們雙擊文字的時(shí)候,文字就會(huì)被選中),比如單擊鏈接可以打開新窗口。
Event.preventDefault() 為 W3C 規(guī)范方法,在 IE 中的實(shí)現(xiàn)方法為 Event.returnValue=false。
http://wiki.jikexueyuan.com/project/fend_note/images/E/event_types.jpg" alt="" />
| 事件類型 | 是否冒泡 | 元素 | 默認(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)不能正常載入時(shí),載入備份替代圖來提供用戶體驗(yàn)。
<img src="http://sample.com/img.png" 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的窗體大小時(shí)會(huì)觸發(fā)事件,scroll 則會(huì)在滑動(dòng)內(nèi)容時(shí)觸發(fā),作用于 Document 則不會(huì)冒泡,作用于內(nèi)部元素則會(huì)冒泡。
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)在子元素直接移動(dòng)不會(huì)觸發(fā)事件,而后者會(huì)觸發(fā)。
mouseleave 與 mouseout 同上相似。
http://wiki.jikexueyuan.com/project/fend_note/images/M/mouse_move_event.jpg" alt="" />
鼠標(biāo)的移動(dòng)過程中會(huì)產(chǎn)生很多事件。事件的監(jiān)察頻率又瀏覽器決定。
例子:從元素 A 上方移動(dòng)過
mousemove -> mouseover(A) -> mouseenter(A) -> mousemove(A) -> mouseout(A) -> mouseleave(A)
例子:點(diǎn)擊元素
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 |
屬性
其用于處理元素獲得或失去焦點(diǎn)的事件。(例如輸入框的可輸入狀態(tài)則為獲得焦點(diǎn),點(diǎn)擊外部則失去焦點(diǎn))
| 事件類型 | 是否冒泡 | 元素 | 默認(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 失去焦點(diǎn)時(shí),focus 獲得焦點(diǎn)時(shí),focusin 即將獲得焦點(diǎn),focusout即將失去焦點(diǎn)。
屬性
一個(gè)元素失去,既另一個(gè)元素獲得焦點(diǎn)。這里的 relatedTarget 則為相對(duì)的那個(gè)元素。
輸入框輸入內(nèi)容則會(huì)觸發(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é)點(diǎn)上(可為元素最近的父節(jié)點(diǎn)也可為上層的其他節(jié)點(diǎn))處理子元素上觸發(fā)的事件,其原理是通過事件流機(jī)制而完成的??梢酝ㄟ^事件對(duì)象中獲取到觸發(fā)事件的對(duì)象(如下所示)。
var elem = document.getElemenyById('id');
elem.addEventListener('click', function(event) {
var e = event || window.event;
var target = e.target || e.srcElement;
// statements
});
優(yōu)點(diǎn)
缺點(diǎn)