首先在介紹DOM事件以前咱們先來認識下DOM的不一樣級別。針對不一樣級別的DOM,咱們的DOM事件處理方式也是不同的。
DOM級別一共能夠分爲4個級別:DOM0級,DOM1級,DOM2級和DOM3級,
而DOM事件分爲3個級別:DOM0級事件處理,DOM2級事件處理和DOM3級事件處理。
以下圖所示:html
其中1級DOM標準中並無定義事件相關的內容,因此沒有所謂的1級DOM事件模型。node
事件指能夠被 JavaScript 偵測到的行爲。即鼠標點擊、頁面或圖像載入、鼠標懸浮於頁面的某個熱點之上、在表單中選取輸入框、確認表單、鍵盤按鍵等操做。事件一般與函數配合使用,當事件發生時函數纔會執行。
事件名稱:click/mouseover/blur("不帶on")響應某個事件的函數就是事件處理程序(事件偵聽器)。
事件處理程序函數名稱:onclick/onmouseove/onblurgit
例子代碼--點擊事件觸發alert函數 <button onclick="alert('hello')"></button>
更多事件類別請參考w3c中關於事件的詳細類別。
JavaScript 事件
JavaScript 事件參考手冊github
事件流指從頁面中接收事件的順序,也可理解爲事件在頁面中傳播的順序。
一點背景:
早期的IE事件傳播方向爲由上至下,即從document逐級向下傳播到目標元素;
而Netscape公司的Netscape Navigator則是朝相反的方向傳播,也就是從目標元素開始向上逐級傳播最終至window。 兩家公司對於事件流出現了截然相反的定義。segmentfault
後來ECMAScript在DOM2中對事件流進行了進一步規範,基本上就是上述兩者的結合。
當事件發生時,最早獲得通知的是window,而後是document,由上至下逐級依次而入,直到真正觸發事件的那個元素(目標元素)爲止,這個過程就是捕獲。
接下來,事件會從目標元素開始起泡,由下至上逐級依次傳播,直到window對象爲止,這個過程就是冒泡。
因此捕獲比冒泡先執行。
其中DOM3級事件在DOM2的基礎之上添加了更多的事件類型。瀏覽器
DOM2級事件規定的事件流包括三個階段:
(1)事件捕獲階段(2)處於目標階段(3)事件冒泡階段。
下面圖片來自:https://www.w3.org/TR/DOM-Lev...app
咱們寫一個例子:以下圖,中間白色區域的盒子分別爲box1,box2...box6,包含控制按鈕設置咱們的事件dom
<div> <h4 id="currentBox">點擊按鈕設置類型後再點擊中心</h4> <button class="btn" id="btnCapture" onclick="setCapture()">設置捕獲</button> <button class="btn" id="btnBubble" onclick="setBubble()">設置冒泡</button> <button class="btn" id="btnAll" onclick="setAll()">設置捕獲和冒泡</button> <button class="btn" onclick="clearAll()">動畫完成後再清除設置</button> </div> <div class="box" id="box1"> <div class="box" id="box2"> <div class="box" id="box3"> <div class="box" id="box4"> <div class="box" id="box5"> <div class="box" id="box6"> 點擊 </div> </div> </div> </div> </div> </div>
大概流程圖以下:函數
演示效果如圖:性能
前面咱們已經說到了,事件處理程序就是響應某個事件的函數,簡單地來講,就是函數。咱們又把事件處理程序稱爲事件偵聽器。事件處理程序是以"on"開頭的,好比點擊事件的處理程序是"onclick",事件處理程序大概有如下5種。
像咱們的第一個例子,就是HTML事件處理程序,它是寫在html裏面的,是全局做用域:
例子代碼--點擊事件觸發alert函數 <button onclick="alert('hello')"></button>
當咱們須要使用一個複雜的函數時,將js代碼寫在這裏面,顯然很不合適,因此有了下面這種寫法:
例子代碼--點擊事件觸發doSomething()函數,這個函數寫在單獨的js文件或<script>之中。 <button onclick="doSomething()"></button>
這樣會出現一個時差問題,當用戶在HTML元素出現一開始就進行點擊,有可能js尚未加載好,這時候就會報錯。但咱們能夠將函數封裝在try-catch塊中來處理:
<button onclick="try{doSomething();}catch(err){}"></button>
同時,一個函數的改變,同時可能會涉及html和js的修改,這樣是很不方便的,綜上,咱們有了DOM0級事件處理程序。
之因此有DOM0級事件處理程序,和咱們以前提到的IE以及Netscape對應事件傳播方向不一樣處理而產生的事件處理程序。
<button id="btn">點擊</button> <script> var btn=document.getElementById("btn"); btn.onclick=function(){ alert("hello"); } </script>
能夠看到button.onclick這種形式,這裏事件處理程序做爲了btn對象的方法,是局部做用域。
因此咱們能夠用
btn.onclick = null;來刪除指定的事件處理程序。
若是咱們嘗試給事件添加兩個事件,如:
<button id="btn">點擊</button> <script> var btn=document.getElementById("btn"); btn.onclick=function(){ alert("hello"); } btn.onclick=function(){ alert("hello again"); } </script>
輸出,hello again,很明顯,第一個事件函數被第二個事件函數給覆蓋掉了,因此,DOM0級事件處理程序不能添加多個,也不能控制事件流究竟是捕獲仍是冒泡。
進一步規範以後,有了DOM2級事件處理程序,其中定義了兩個方法:
addEventListener() ---添加事件偵聽器
removeEventListener() ---刪除事件偵聽器
具體用法看
1.https://developer.mozilla.org...
2.https://developer.mozilla.org...
函數均有3個參數,
第一個參數是要處理的事件名(不帶on前綴的纔是事件名)
第二個參數是做爲事件處理程序的函數
第三個參數是一個boolean值,默認false表示使用冒泡機制,true表示捕獲機制。
<button id="btn">點擊</button> <script> var btn=document.getElementById("btn"); btn.addEventListener('click',hello,false); btn.addEventListener('click',helloagain,false); function hello(){ alert("hello"); } function helloagain(){ alert("hello again"); } </script>
這時候兩個事件處理程序都可以成功觸發,說明能夠綁定多個事件處理程序,可是注意,若是定義了一摸同樣時監聽方法,是會發生覆蓋的,即一樣的事件和事件流機制下相同方法只會觸發一次,好比:
<button id="btn">點擊</button> <script> var btn=document.getElementById("btn"); btn.addEventListener('click',hello,false); btn.addEventListener('click',hello,false); function hello(){ alert("hello"); } </script>
removeEventListener()的方法幾乎和添加時用法一摸同樣:
<button id="btn">點擊</button> <script> var btn=document.getElementById("btn"); btn.addEventListener('click',hello,false); btn.removeEventListener('click',hello,false); function hello(){ alert("hello"); } </script>
這樣的話,事件處理程序只會執行一次。
可是要注意,若是同一個監聽事件分別爲「事件捕獲」和「事件冒泡」註冊了一次,一共兩次,這兩次事件須要分別移除。二者不會互相干擾。
這時候的this指向該元素的引用。
這裏事件觸發的順序是添加的順序。
對於 Internet Explorer 來講,在IE 9以前,你必須使用 attachEvent 而不是使用標準方法 addEventListener。
IE事件處理程序中有相似與DOM2級事件處理程序的兩個方法:
1.attachEvent()
2.detachEvent()
它們都接收兩個參數:
1.事件處理程序名稱。如onclick、onmouseover,注意:這裏不是事件,而是事件處理程序的名稱,因此有on。
2.事件處理程序函數。
之因此沒有和DOM2級事件處理程序中相似的第三個參數,是由於IE8及更早版本只支持冒泡事件流。
removeEventListener()的方法幾乎和添加時用法一摸同樣:
<button id="btn">點擊</button> <script> var btn=document.getElementById("btn"); btn.attachEvent('onclick',hello); btn.detachEvent('onclick',hello); function hello(){ alert("hello"); } </script>
這裏事件觸發的順序不是添加的順序而是添加順序的相反順序。
使用 attachEvent 方法有個缺點,this 的值會變成 window 對象的引用而不是觸發事件的元素。
爲了兼容IE瀏覽器和標準的瀏覽器,咱們須要編寫通用的方法來處理: 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; } }, removeHandler: 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; } } };
這一部分須要建立兩個方法:
addHandler() --這個方法職責是視狀況來使用DOM0級、DOM2級、IE事件處理程序來添加事件。
removeHandler()--這個方法就是移除使用addHandler添加的事件。
這兩個方法接收相同的三個參數:
1.要操做的元素--經過dom方法獲取
2.事件名稱--注意:沒有on,如"click"、"mouseover"
3.事件處理程序函數--對應的函數
使用:
<button id="btn">點擊</button> <script> var btn=document.getElementById("btn"); EventUtil.addHandler(btn,'click',hello); EventUtil.removeHandler(btn,'click',hello); function hello(){ alert("hello"); } </script>
事件對象是用來記錄一些事件發生時的相關信息的對象,但事件對象只有事件發生時纔會產生,而且只能是事件處理函數內部訪問,在全部事件處理函數運行結束後,事件對象就被銷燬!
屬性和方法如圖,詳細請查看如下連接:
1.HTML DOM Event 對象:http://www.w3school.com.cn/js...
2.詳細介紹請查看:http://www.jb51.net/article/9...
下面是一個例子:
<button id="btn">點擊</button> <script> var btn=document.getElementById("btn"); btn.ddEventListener('click', doCurrent, true); // 判斷事件的屬性 function doCurrent(event) { //獲取當前事件觸發的div var target = event.currentTarget; //經過判斷事件的event.eventPhase屬性返回事件傳播的當前階段 //1:捕獲階段、2:正常事件派發和3:起泡階段。 //獲得當前階段和id值並輸出 var msg = (event.eventPhase == 1 ? '捕獲階段:' : '冒泡階段:')+ target.attributes["id"].value;; console.log(msg); } </script>
在這個例子裏,咱們用到了currentTarget、eventPhase 屬性。
Event對象主要有如下兩個方法,用於處理事件的傳播(冒泡、捕獲)和事件的取消。
stopPropagation()——冒泡機制下,阻止事件的進一步往上冒泡
var btn1=document.getElementById("btn1"); var content=document.getElementById("content"); btn1.addEventListener("click",function(event){ alert("btn1"); event.stopPropagation(); },false); content.addEventListener("click",function(){ alert("content"); },false); //這裏會輸出btn1,阻止了向content的冒泡
preventDefault()——用於取消事件的默認操做,好比連接的跳轉或者表單的提交,主要是用來阻止標籤的默認行爲
<a id="go" href="https://www.baidu.com/">禁止跳轉</a> var go = document.getElementById('go'); function goFn(event) { event.preventDefault(); // 不會跳轉 } go.addEventListener('click', goFn, false);
固然,事件對象也存在必定的兼容性問題,在IE8及之前本版之中,經過設置屬性註冊事件處理程序時,調用的時候並未傳遞事件對象,須要經過全局對象window.event來獲取。解決方法以下:
function getEvent(event) { event = event || window.event; }
在IE瀏覽器上面是event事件是沒有preventDefault()這個屬性的,因此在IE上,咱們須要設置的屬性是returnValue
window.event.returnValue=false
stopPropagation()也是,因此須要設置cancelBubble,cancelBubble是IE事件對象的一個屬性,設置這個屬性爲true能阻止事件進一步傳播。
event.cancelBubble=true
事件委託就是利用事件冒泡,只指定一個事件處理程序,就能夠管理某一類型的全部事件。
例子說明,咱們爲ul添加新的li,其中對li標籤元素綁定了click事件,可是發現,後增長的元素沒有辦法觸發咱們的click事件。
<button id="btnAdd">添加</button> <ul id="ulList"> <li>1</li> <li>2</li> <li>3</li> </ul> <script> var btnAdd = document.getElementById('btnAdd'); var ulList = document.getElementById('ulList'); var list = document.getElementsByTagName('li'); var num = 3; btnAdd.onclick = function () { num++; var li = document.createElement('li'); li.innerHTML = num; ulList.appendChild(li) } for (i = 0; i < list.length; i++) { list[i].onclick = function(){ alert(this.innerHTML); } } </script>
這是由於若是事件涉及到更新HTML節點或者添加HTML節點時,新添加的節點沒法綁定事件,更新的節點也是沒法綁定事件,表現的行爲是沒法觸發事件。
其中一種解決方法是,添加子節點的時候,再次爲其添加監聽事件
<button id="btnAdd">添加</button> <ul id="ulList"> <li>1</li> <li>2</li> <li>3</li> </ul> <script> var btnAdd = document.getElementById('btnAdd'); var ulList = document.getElementById('ulList'); var list = document.getElementsByTagName('li'); var num = 3; function doclick() { for (i = 0; i < list.length; i++) { list[i].onclick = function () { alert(this.innerHTML); } } } doclick(); btnAdd.onclick = function () { num++; var li = document.createElement('li'); li.innerHTML = num; ulList.appendChild(li); doclick(); } </script>
這也是問題所在:
1.首先咱們屢次操做DOM獲取元素,這樣勢必會下降瀏覽器處理性能
2.事件不具備繼承性,若是咱們動態在頁面中添加了一個元素,那麼還須要從新走一遍上述程序爲其添加監聽事件
那麼有沒有更好的方法呢?根據事件的冒泡原理,咱們還能夠實現另一個很重要的功能:事件委託。
咱們只監聽最外層的元素,而後在事件函數中根據事件來源進行不一樣的事件處理。這樣,咱們添加事件監聽時只須要操做一個元素,極大的下降了DOM訪問,而且不用再給新增的元素添加監聽事件了,由於元素的事件會冒泡到最外層,被咱們截獲。
<button id="btnAdd">添加</button> <ul id="ulList"> <li>1</li> <li>2</li> <li>3</li> </ul> <script> var btnAdd = document.getElementById('btnAdd'); var ulList = document.getElementById('ulList'); var num = 3; ulList.onclick = function(event){ var event = event || window.event; var target = event.target || event.srcElement; if(target.nodeName.toLowerCase() == 'li'){ alert(target.innerHTML); } } btnAdd.onclick = function () { num++; var li = document.createElement('li'); li.innerHTML = num; ulList.appendChild(li); doclick(); } </script>
這裏用父級ul作事件處理,當li被點擊時,因爲冒泡原理,事件就會冒泡到ul上,由於ul上有點擊事件,因此事件就會觸發,固然,這裏當點擊ul的時候,也是會觸發的,因此要判斷點擊的對象究竟是不是li標籤元素。
Event對象提供了一個屬性叫target,能夠返回事件的目標節點,咱們成爲事件源,也就是說,target就能夠表示爲當前的事件操做的dom,可是不是真正操做dom,固然,這個是有兼容性的,標準瀏覽器用ev.target,IE瀏覽器用event.srcElement,此時只是獲取了當前節點的位置,並不知道是什麼節點名稱,這裏咱們用nodeName來獲取具體是什麼標籤名,這個返回的是一個大寫的,咱們須要轉成小寫再作比較(習慣問題)。
這樣,咱們就實現了咱們的事件委託,固然,不是全部的事件都是能夠委託的。
適合用事件委託的事件:click,mousedown,mouseup,keydown,keyup,keypress。
當用事件委託的時候,根本就不須要去遍歷元素的子節點,只須要給父級元素添加事件就行了,新增長的節點也能夠觸發事件效果。
參考:
1.http://www.cnblogs.com/souven...
2.https://www.cnblogs.com/st-le...
3.https://segmentfault.com/a/11...
4.http://www.jb51.net/article/9...
5.http://www.w3school.com.cn/js...
6.http://www.jb51.net/article/8...
7.http://www.jb51.net/article/9...