DOM 事件

2018-07-10 15:14 更新
Table of Contents generated with DocToc

DOM 事件

何為 DOM 事件,HTML DOM 使JavaScript 有能力對 HTML 事件做出反應(yīng)。(例如,點擊 DOM 元素,鍵盤被按,輸入框輸入內(nèi)容以及頁面加載完畢等)

事件流

一個 DOM 事件可以分為捕獲過程、觸發(fā)過程冒泡過程。 DOM 事件流為 DOM 事件的處理及執(zhí)行的過程。下面以一個<a>元素被點擊為例。

  1. [紅虛線]Capture Phase(事件捕獲過程)當(dāng) DOM 事件發(fā)生時,它會從window節(jié)點一路跑下去直到觸發(fā)事件元素的父節(jié)點為止,去捕獲觸發(fā)事件的元素。
  2. [紅綠實線]Target Phase(事件觸發(fā)過程)當(dāng)事件被捕獲之后就開始執(zhí)行事件綁定的代碼
  3. [綠虛線]Bubble Phase(冒泡過程)當(dāng)事件代碼執(zhí)行完畢后,瀏覽器會從觸發(fā)事件元素的父節(jié)點開始一直冒泡到window元素(即元素的祖先元素也會觸發(fā)這個元素所觸發(fā)的事件

關(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])
  • evenTarget 表示要綁定事件的DOM元素
  • type 表示要綁定的事件,如:"click"
  • listener 表示要綁定的函數(shù)
  • useCapture 可選參數(shù),表示是否捕獲過程

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]);
  • evenTarget 表示要綁定事件的DOM元素
  • type 表示要綁定的事件,如:"click"
  • listener 表示要綁定的函數(shù)
  • useCapture 可選參數(shù),表示是否捕獲過程
// 獲取元素
var elem = document.getElemenyById('id');

// 取消事件
elem.removeEventListener('click', clickHandler, false);

// 第二種方式。不建議使用
elem.onclick = null;

觸發(fā)事件

點擊元素,按下按鍵均會觸發(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)。

兼容低版本代碼實現(xiàn)

注冊事件

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
};

屬性和方法

通用屬性和方法

屬性

  • type 事件類型
  • target(srcElement IE 低版本) 事件觸發(fā)節(jié)點
  • currentTarget 處理事件的節(jié)點

方法

  • stopPropagation 阻止事件冒泡傳播
  • preventDefault 阻止默認(rèn)行為
  • stopImmediatePropagation 阻止冒泡傳播
阻止事件傳播

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)行為(點擊一個鏈接的時候,鏈接默認(rèn)就會打開。當(dāng)我們雙擊文字的時候,文字就會被選中),比如單擊鏈接可以打開新窗口。

Event.preventDefault() 為 W3C 規(guī)范方法,在 IE 中的實現(xiàn)方法為 Event.returnValue=false。

事件分類

Event

事件類型是否冒泡元素默認(rèn)事件元素例子
loadNOWindow, Document, ElementNonewindow, image, iframe
unloadNOWindow, Document, ElementNonewindow
errorNOWindow, ElementNonewindow, image
selectNOElementNoneinput, textarea
abortNOWindow, ElementNonewindow, image
window
  • load 頁面全部加載完畢
  • unload 離開本頁之前的卸載
  • error 頁面異常
  • abort 取消加載
image
  • load 圖片加載完畢
  • error 圖標(biāo)加載錯誤
  • abort 取消圖標(biāo)加載

在目標(biāo)圖標(biāo)不能正常載入時,載入備份替代圖來提供用戶體驗。

<img src="http://sample.com/img.png" rel="external nofollow"  onerror="this.src='http://sample.com/default.png'">

UIEvent

事件類型是否冒泡元素默認(rèn)事件元素例子
resizeNOWindow, ElementNonewindow, iframe
scrollNO/YESDocument, ElementNonedocument, div

NOTE:resize 為改變?yōu)g覽器或iframe的窗體大小時會觸發(fā)事件,scroll 則會在滑動內(nèi)容時觸發(fā),作用于 Document 則不會冒泡,作用于內(nèi)部元素則會冒泡。

MouseEvent

DOM 事件中最常見的事件之一。

事件類型是否冒泡元素默認(rèn)事件元素例子
clickYESElementfocus/activationdiv
dbclickYESElementfocus/activation/selectdiv
mousedownYESElementdrag/scroll/text selectiondiv
mosuemoveYESElementNonediv
mouseoutYESElementNonediv
mouseoverYESElementNonediv
mouseupYESElementcontext menudiv
mouseenterNOElementNonediv
mouseleaveNOElementNonediv

NOTE:mouseenter 與 mouseover 的區(qū)別為前者在鼠標(biāo)在子元素直接移動不會觸發(fā)事件,而后者會觸發(fā)。mouseleave 與 mouseout 同上相似。

屬性
  • clientX, clientX
  • screenX, screenY
  • ctrlKey, shiftKey, altKey, metaKey 如果被按下則為真(true)
  • button(0, 1, 2) 鼠標(biāo)的間位

MouseEvent 順序

鼠標(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);

滾輪事件(Wheel)

事件類型是否冒泡元素默認(rèn)事件元素例子
wheelYESElementscroll or zoom documentdiv

屬性

  • deltaMode 鼠標(biāo)滾輪偏移量的單位
  • deltaX
  • deltaY
  • deltaZ

FocusEvent

其用于處理元素獲得或失去焦點的事件。(例如輸入框的可輸入狀態(tài)則為獲得焦點,點擊外部則失去焦點)

事件類型是否冒泡元素默認(rèn)事件元素例子
blurNOWindow, ElementNonewindow, input
focusNOWindow, ElementNonewindow, input
focusinNOwindow, ElementNonewindow, input
focusoutNOwindow, ElementNonewindow, input

NOTE:blur 失去焦點時,focus 獲得焦點時,focusin 即將獲得焦點,focusout即將失去焦點。

屬性

一個元素失去,既另一個元素獲得焦點。這里的 relatedTarget 則為相對的那個元素。

  • relatedTarget

InputEvent

輸入框輸入內(nèi)容則會觸發(fā)輸入事件。

事件類型是否冒泡元素默認(rèn)事件元素例子
beforeInputYESElementupdate DOM Elementinput
inputYESElementNoneinput

NOTE:beforeInput 為在按鍵按下后即將將輸入字符顯示之前生成的事件。

NOTE+:IE 并沒有 InputEvent 則需使用 onpropertychange(IE) 來代替。

KeyboardEvent

其用于處理鍵盤事件。

事件類型是否冒泡元素默認(rèn)事件元素例子
keydownYESElementbeforeInput/input/focus/blur/activationdiv, input
keyupYESElementNonediv, input

屬性

  • key 按下的鍵字符串
  • code
  • ctrlKey, shiftKey, altKey, metaKey
  • repeat 代表按鍵不松開為 true
  • keyCode
  • charCode
  • which

事件代理

事件代理是指在父節(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)點

  • 需要管理的事件處理函數(shù)更少
  • 內(nèi)存分配更少,更高效
  • 增加與刪除子節(jié)點可以不額外處理事件

缺點

  • 事件管理的邏輯變的復(fù)雜(因為冒泡機制)


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號