前言: JAVASCRIPT與HTML之間的交互是經過事件來實現的。事件,就是文檔或瀏覽器窗口中發生的一些特定交互瞬間。可使用偵聽器( 或處理程序 )來預約事件,以便事件發生時執行相應的代碼。這種在傳統軟件工程中被稱爲觀察員模式的模型,支持頁面的行爲與頁面的外觀之間的鬆散耦合。javascript
事件最先是在IE3和Netscape Navigator 2中出現的,當時是做爲分擔服務器運算負載的一種手段。在IE4和Navigator 4發佈時,這兩種瀏覽器都提供了類似可是不一樣的API, 這些API並存經歷了好幾個主要版本。DOM2 級規範開始嘗試以一種符合邏輯的方式來標準化DOM事件。IE9, Firefox,Opera,Safari和Chrome全都已經實現了「DOM2級事件」模塊的核心部分。IE8是最後一個仍然使用專用事件系統的主要瀏覽器。html
<一> 事件流java
事件流描述的是從頁面中接受事件的順序。但有意識的是,IE和Navigator 開發團隊竟然提出了差很少是徹底相反的事件流概念。IE的事件流是冒泡流, 而Navigator 的事件流是捕獲流。web
1.1 事件冒泡瀏覽器
IE的事件流叫作事件冒泡(event bubbling), 即事件開始時由最具體的元素( 文檔中嵌套層次最深的那個節點 )接受,而後逐級向上傳播到較爲不具體的節點(文檔)。服務器
舉例click事件dom
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div onclick="test()">事件</div> </body> <script type="text/javascript"> function test() { alert('test event bubbling') } </script> </html>
click事件傳播順序: div --> body --> html --> document函數
click事件首先在<div>元素上發生, 而這個元素就是咱們單擊的元素。而後,click事件沿着DOM樹向上傳播,在每個節點上都發生,直到傳播到document對象。性能
備註: 全部現代瀏覽器都支持事件冒泡,可是在具體實現上仍是有一些差異。IE5.5 及更早版本中的事件冒泡會跳過<html>元素 (從<body>直接跳轉到<document>)。IE9,Firefox,Chrome和Safari則將事件一直冒泡到window對象。學習
1.2 事件捕獲
事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件。事件捕獲的用意在於事件到達預約目標以前捕獲它。
雖然事件捕獲是Navigator Communcator惟一支持的事件流模型,可是IE9,Safari,Chrome,Opera和Firefox目前也都支持這種事件流模型。
1.3 DOM事件流
DOM2級事件: 規定的事件流包括三個階段: 事件捕獲階段,處於目標階段和事件冒泡階段。
首先發生的是事件捕獲,爲截取事件提供了機會。而後是實際目標接收到事件。最後一個階段是冒泡階段,能夠在這個階段對事件做出響應。具體流經以下圖所示。
<二> 事件處理程序
事件就是用戶或瀏覽器自身執行的某種動做。諸如click,load和mouseover,都是事件的名字。而響應某個事件的函數就叫作事件處理程序(或事件偵聽器)。事件處理程序的名字以‘on’,所以click事件的事件處理程序就是onclick。爲事件指定處理程序的方式有好幾種
2.1 HTML事件處理程序
直接綁定在元素上,或者引入函數名
<body> <input type="button" onclick="alert('test')" value="按鈕"> <div onclick="test()">事件</div> </body> <script type="text/javascript"> function test() { alert('test event bubbling') } </script>
2.2 DOM0級事件處理程序
經過JavaScript指定事件處理程序的傳統方式,就是將一個函數賦值給一個事件處理程序屬性。這種爲事件處理程序賦值方法是在第四代web瀏覽器中出現的,並且至今仍然爲全部瀏覽器所支持。緣由一:簡單;緣由二:具備跨瀏覽器的優點。要使用JavaScript指定事件處理程序,首先必須取得一個要操做的對象的引用。
每一個元素(包括window和document)都有本身的事件處理程序,這些屬性一般所有小寫,例如
<body> <div id="btn">提示</div> </body> <script type="text/javascript"> var btn = document.getElementById('btn'); btn.onclick = function () { alert('事件綁定成功') } </script>
若是要解除事件綁定
btn.onclick = null;
2.3.1 DOM2級事件處理程序
"DOM2級事件" 定義了兩個方法,用於處理指定事件和刪除事件處理程序的操做,addEventListener()和removeEventListener()。全部DOM節點中都包含這兩個方法,而且他們都接受3個參數:要處理事件名,做爲事件處理程序的函數和一個布爾值。 最後這個參數若是是true,表示在捕獲階段調用事件處理程序;若是是false,表示在冒泡階段調動事件處理程序。
能夠爲一個元素添加多個事件處理程序,事件處理程序會按照它們添加的順序依次執行。
<body> <div id="btn">提示</div> </body> <script type="text/javascript"> var btn = document.getElementById('btn'); btn.addEventListener('click',function () { console.log('第一個事件'); },false); btn.addEventListener('click',function () { console.log('hello world'); },false); </script>
2.3.2 IE事件處理程序
IE實現了與DOM中類型的兩個方法:attachEvent()和detachEvent()。這兩個方法都接受相同的參數:事件處理程序名稱,事件處理程序函數。因爲IE8及更早版本只支持冒泡事件,因此經過attachEvent()添加的事件出現程序都會被添加到冒泡階段
在IE中使用attachEvent()與DOM0級方法的主要區別在於事件處理程序的做用域。在使用DOM0級方法的狀況下,事件處理程序會在其所屬元素的做用域運行;在使用attachEvent()方法的狀況下,事件處理程序會在全局做用域下運行,所以this等於window。
<body> <div id="btn">提示IE</div> </body> <script type="text/javascript"> var btn = document.getElementById('btn'); btn.attachEvent('onclick',function () { console.log(this); console.log('第一個事件'); }); btn.attachEvent('onclick',function () { console.log('hello world'); }); </script>
注意:
1. IE事件處理程序不是以添加它們的順序執行,而是以相反的順序執行;
2. 綁定事件時,IE要添加 ‘on’。
2.3.3 移除事件處理程序
要移除事件處理程序,要傳入函數名。不能移除匿名函數,移除匿名函數時無效的。
<body> <div id="btn">提示</div> </body> <script type="text/javascript"> var btn = document.getElementById('btn'); var handler = function () { console.log('第一個事件'); }; btn.addEventListener('click',handler,false); btn.removeEventListener('click',handler,false); </script>
2.3.4 老是所述,跨瀏覽器的事件處理程序。
var EventUtil = {
addHandler:function (element, type, handler) {
if(element.addEventListener){
element.addEventListener(type,handler,false)
}else if(element.attachEvent){
element.attachEvent('on'+type,handler)
}else {
element['on'+type] = handler;
}
},
rmoveHandler:function (element, type, handler) {
if(element.removeEventListener){
element.removeEventListener(type,handler,false);
}else if(element.detachEvent){
element.detachEvent('on'+type,handler);
}else {
element['on'+type] = null;
}
},
};
<三> 事件對象
3.1 在出發DOM上的某個事件時,會產生一個事件對象event, 這個對象種包含着全部與事件有關的信息。包括致使事件的元素,事件的類型,以及其餘與特定事件相關的信息。
附件:全部event的詳細信息: W3C_HTML DOM Event 對象
<四> 事件類型
4.1 判斷是否支持DOM2,DOM3事件
document.implementation.hasFeature("HTMLEvent","2.0");
document.implementation.hasFeature("UIEvent","3.0");
備註: 只有根據「DOM2/3級事件」實現這些事件(load,unload,abort,resoze,scroll......)的瀏覽器纔會返回true。而以非標準方式支持這些事件的瀏覽器則返回false。
4.2 鼠標和滾輪事件
4.2.1 事件說明
click:在用戶單機主鼠標按鈕(通常是左邊的按鈕)或者按下回車鍵時出發。這一點對確保易訪問性很重要,意味着onclick事件處理程序便可以經過鍵盤也能夠經過鼠標執行。
dbclick: 在用戶雙擊主鼠標(通常是左邊的按鈕)時觸發。從技術上說,這個事件並非DOM2級事件規範種規定的,但鑑於它獲得了普遍的支持,因此DOM3級事件將其歸入了規範。
mousedown: 在用戶按下了任意鼠標時觸發。不能經過鍵盤觸發該事件。
mouseenter: 在鼠標光標從元素外部首次移動到元素範圍以內時觸發。這個事件不冒泡,並且在光標移動到後代元素上不會觸發。DOM2級事件並無定義這個事件,但DOM3級事件將它歸入了規範。IE9,Firefox9+和Opera支持這個事件。
mouseleave:在位於元素上方的鼠標光標移動到元素範圍以外時觸發。這個事件不冒泡,並且在光標移動到後代元素上不會觸發。DOM2級事件並無定義這個事件,但DOM3級事件將它歸入了規範。IE9,Firefox9+和Opera支持這個事件。
mousemove:當鼠標指針在元素內部移動時重複地觸發。不能經過鍵盤觸發該事件。
mouseout:在鼠標指針位於一個元素上方,而後用戶將其移入另外一個元素時觸發。又移入的另外一個元素可能位於前一個元素的外部,也多是這個元素的子元素。不能經過鍵盤觸發該事件。
mouseover:在鼠標指針位於一個元素外部,而後用戶將其首次移入另外一個元素邊界以內時觸發。不能經過鍵盤觸發該事件。
mouseup:在用戶釋放鼠標按鈕指針時觸發。不能經過鍵盤觸發該事件。
備註: 只有在同一個元素上觸發mousedown和mouseup事件,纔會觸發cilick事件;若是mousedown或mouseup中的一個被取消,就不觸發click事件。
相似地,只有觸發兩次click事件,纔會觸發一次dbclick,若是代碼組織了連續兩次click事件(多是直接取消click,也可能經過取消mousedown或mouseup間接實現),那麼就不會觸發dbclick。這四個事件觸發的順序始終以下:
1. mousedown 2. mouseup 3. click 4. mousedown 5. mouseup 6. click 7. dbclick
click和dbclick事件都依賴於其餘事件先行觸發,而mousedown和mouseup則不受其餘影響。
IE8及以前版本中的實現有個bug,觸發的事件順序以下:
1. mousedown 2. mouseup 3. click 4. mouseup 5. dbclick
4.2.2 修改鍵
雖然鼠標事件主要是使用鼠標來觸發,可是在按下鼠標時鍵盤上的某些鍵盤的狀態也能夠影響到所要採起的操做。這些修改鍵就是Shift,Ctrl,Alt和Meta(蘋果Cmd鍵),它們常常被用來修改鼠標事件的行爲。DOM爲此規定了4個屬性,表示這些修改鍵的狀態:shiftKey,ctrlKey,altKey和MetaKey。這些屬性中包含的都是布爾值,若是相應的鍵被按下了,則值爲true,不然爲false。當某個鼠標事件發生時,經過檢測這幾個屬性就能夠肯定用戶是否同時按下了其中的鍵。示例以下:
<body> <div id="btn">提示</div> </body> <script src="./js/base.js"></script> <script type="text/javascript"> var btn = document.getElementById('btn'); EventUtil.addHandler(btn,'click',function (event) { event = EventUtil.getEvent(event); var keys = []; if(event.shiftKey){ keys.push('shift'); } if(event.ctrlKey){ keys.push('ctrl') } if(event.altKey){ keys.push('alt') } if(event.metaKey){ keys.push('meta') } }) </script>
4.2.3 鼠標按鈕
只有在主鼠標按鈕被單擊(或鍵盤迴車鍵被按下)時纔會觸發click事件,所以檢測按鈕的信息並非必要的。但對於mousedown和mouseup事件來講,則在其event對象存在一個button屬性,表示按下或釋放的按鈕。
DOM的button屬性可能有以下3個值:0表示主鼠標按鈕,1表示中間的鼠標按鈕(鼠標滾輪按鈕),2 表示次鼠標按鈕。 在常規的設置中,主鼠標按鈕就是鼠標左鍵,而次鼠標按就是鼠標右鍵。
IE8及以前版本也提供了button屬性,可是這個屬性值與DOM的button有很大差別。
0:表示沒有按下按鈕;
1:表示按下了主鼠標按鈕;
2:表示按下了次鼠標按鈕;
3:表示同時按下了主鼠標按鈕,次鼠標按鈕;
4:表示按下了中間鼠標按鈕;
5:表示同時按下了主鼠標按鈕和中間鼠標按鈕;
6:表示同時按下了次鼠標按鈕和中間鼠標按鈕;
7:表示同時按下了三個鼠標按鈕。
getButton:function (event) { if(document.implementation.hasFeature("MouseEvent","2.0")){ return event.button; }else { switch (event.button){ case 0: case 1: case 3: case 5: case 7: return 0; case 2: case 6: return 2; case 4: return 1; } } },
4.2.4 相關元素
DOM經過event對象的releateTarget屬性提供了相關元素信息。這個屬性只對於mouseover和mouseout事件才包含值,對於其餘事件,這個屬性的值是null。
IE8及以前版本不支持releateTarget屬性,但提供了保存一樣信息的不一樣屬性。在mouseover事件觸發時,IE的fromElement屬性中保存了相關元素;在mouseout事件觸發時,IE的toElement屬性中保存了相關元素。
IE9及之後IE瀏覽器支持全部這些屬性。
getRelateTarget:function (event) { if(event.relatedTarget){ return event.relatedTarget; }else if(event.toElement){ return event.toElement; }else if(event.fromElement){ return event.fromElement; }else { return null; } },
<五> 綜上所述,總結概括了通用的事件偵聽器函數整體封裝,以供之後學習和使用
var EventUtil = { addHandler:function (element, type, handler) { if(element.addEventListener){ element.addEventListener(type,handler,false) }else if(element.attachEvent){ element.attachEvent('on'+type,handler) }else { element['on'+type] = handler; } }, rmoveHandler:function (element, type, handler) { if(element.removeEventListener){ element.removeEventListener(type,handler,false); }else if(element.detachEvent){ element.detachEvent('on'+type,handler); }else { element['on'+type] = null; } }, getEvent:function (event) { return event ? event : window.event; }, getTarget:function (event) { return event.target || event.srcElement; }, preventDefault:function (event) { if(event.preventDefault){ event.preventDefault(); }else{ event.returnValue = false; } }, stopPropagation:function (event) { if(event.stopPropagation){ event.stopPropagation(); }else{ event.cancelBubble = true; } }, getRelateTarget:function (event) { if(event.relatedTarget){ return event.relatedTarget; }else if(event.toElement){ return event.toElement; }else if(event.fromElement){ return event.fromElement; }else { return null; } }, getButton:function (event) { if(document.implementation.hasFeature("MouseEvent","2.0")){ return event.button; }else { switch (event.button){ case 0: case 1: case 3: case 5: case 7: return 0; case 2: case 6: return 2; case 4: return 1; } } }, getCharCode:function (event) { if(typeof event.charCode === 'number'){ return event.charCode; }else { return event.keyCode; } } };
<六> 事件委託
推薦參考學習地址: js中的事件委託或是事件代理詳解
因爲事件處理程序能夠爲現代web應用程序提供交互能力,所以許多開發人員會不分青紅皁白地向頁面中添加大量地處理程序. 在建立GUI地語言(C#)中, 爲GUI中地每一個按鈕添加一個onclick事件處理程序是司空見慣地事情,並且這樣作也不會致使什麼問題. 但是在JavaScript中, 添加到頁面上的事件處理程序數量將直接關係到頁面到總體性能. 致使這一問題到緣由不少. 首先,每一個函數都是對象, 都會佔用內存, 內存中的對象越多性能就越差. 其次, 必須事先指定全部事件處理程序而致使的DOM訪問次數, 會延遲整個頁面的交互就緒時間. 事實上, 從如何利用好事件處理程序的角度觸發, 仍是有一些方法可以提高性能.
示例擴展:
<body> <ul id="test"> <li style="height: 56px;background-color: red"> <p style="background-color: #1F89bf;height: 30px;">11111111111</p> </li> <li> <div> <p> <div> <span> <span> <span> <span>99999</span> </span> </span> </span> </div> </p> </div> </li> </ul> </body> <script type="text/javascript"> window.onload = function () { var oUl = document.getElementById('test'); oUl.addEventListener('click',function(event){ var target = EventUtil.getEvent(event); var i = 0; while(target !== oUl ){ i++; if(target.tagName.toLowerCase() === 'li'){ // 處理相應的函數... break; } target = target.parentNode; } console.log(i,'num'); // 7 "num" }) } </script>