一、事件流(捕獲,冒泡)
事件流:指從頁面中接收事件的順序,有冒泡流和捕獲流。
當頁面中發生某種事件(比如鼠標點擊,鼠標滑過等)時,毫無疑問子元素和父元素都會接收到該事件,可具體順序是怎樣的呢?冒泡和捕獲則描述了兩種不同的順序。
DOM2級事件規定事件流包括三個階段,如圖:

假如我們點擊一個div, 實際上是先點擊document,然后點擊事件傳遞到div,而且並不會在這個div就停下,div有子元素就還會向下傳遞,最后又會冒泡傳遞回document,如上圖
為了兼容更多的瀏覽器,非特殊情況一般我們都是把事件添加到在事件冒泡階段。
二、事件處理程序
DOM0級事件處理程序
例子:
1 var btn5 = document.getElementById('btn5'); 2 btn5.onclick=function(){ 3 console.log(this.id);//btn5 4 };
注意:基於DOM0的事件,對於同一個dom節點而言,只能注冊一個,后邊注冊的 同種事件 會覆蓋之前注冊的。利用這個原理我們可以解除事件,btn5.onclick=null;其中this就是綁定事件的那個元素;
以這種方式添加的事件處理程序會在事件流的冒泡階段被處理;
DOM2級事件處理程序
DOM2支持同一dom元素注冊多個同種事件,事件發生的順序按照添加的順序依次觸發(IE是相反的)。
DOM2事件通過addEventListener和removeEventListener管理
//addEventListener(eventName,handlers,boolean);removeEventListener(),兩個方法都一樣接收三個參數,第一個是要處理的事件名,第二個是事件處理程序,第三個值為false時表示在事件冒泡階段調用事件處理程序,一般建議在冒泡階段使用,特殊情況才在捕獲階段;
注意:通過addEventListener()
添加的事件處理程序只能用removeEventListener()
來移除,並且移除時傳入的參數必須與添加時傳入的參數一樣;比如
例子:
1 var btn2 = document.getElementById('btn2'); 2 var handlers = function () { 3 console.log(this.id); 4 }; 5 6 btn2.addEventListener('click',handlers,false); 7 8 btn2.removeEventListener('click',handlers.false);
IE事件處理程序
//IE事件處理程序(IE和Opera支持)
//IE用了attachEvent(),detachEvent(),接收兩個參數,事件名稱和事件處理程序,通過attachEvent()添加的事件處理程序都會被添加到冒泡階段,所以平時為了兼容更多的瀏覽器最好將事件添加到事件冒泡階段,IE8及以前只支持事件冒泡;
//IE用了attachEvent(),detachEvent(),接收兩個參數,事件名稱和事件處理程序,通過attachEvent()添加的事件處理程序都會被添加到冒泡階段,所以平時為了兼容更多的瀏覽器最好將事件添加到事件冒泡階段,IE8及以前只支持事件冒泡;
例子:
1 var btn3 = document.getElementById('btn3'); 2 var handlers2=function(){ 3 console.log(this===window);//true,注意attachEvent()添加的事件處理程序運行在全局作用域中; 4 }; 5 btn3.attachEvent('onclick',handlers2);
跨瀏覽器事件處理程序
1 //創建的方法是addHandlers(),removeHandlers(),這兩個方法屬於一個叫EventUtil的對象;但是這個沒有考慮到IE中作用域的問題,不過就添加和移除事件還是足夠的。 2 3 var EventUtil = { 4 addHandlers: function (element, type, handlers) { 5 if (element.addEventListener) { 6 element.addEventListener(type, handlers, false); 7 } else if (element.attachEvent) { 8 element.attachEvent(on + type, handlers); 9 } else { 10 element['on' + type] = handlers; 11 } 12 }, 13 removeHandlers: function (element, type, handlers) { 14 if (element.removeEventListener) { 15 element.removeEventListener(type, handlers, false); 16 } else if (element.detachEvent) { 17 element.detachEvent(on + type, handlers); 18 } else { 19 element['on' + type] = null; 20 } 21 } 22 };
例子:
1 var btn4=document.getElementById('btn4'); 2 var handlers3=function(){ 3 console.log('123') 4 }; 5 EventUtil.addHandlers(btn4,'click',handlers3); 6 //…… 7 EventUtil.removeHandlers(btn4,'click',handlers3);
在同一個對象上注冊事件,並不一定按照注冊順序執行,冒泡或捕獲模式會影響其被觸發的順序;
三、事件對象
兼容觸發DOM上的某個事件時,會產生一個事件對象event,這個對象中包含了所有與事件有關的信息,比如導致事件的元素target,事件的類型,及其他特定的相關信息。例如鼠標操作導致的事件對象中會包含鼠標的位置,單雙擊等,而鍵盤操作導致的事件對象會包含按下的鍵等信息;
事件被觸發時,會默認給事件處理程序傳入一個參數e , 表示事件對象;通過e,我們可以獲得其中包含的與事件有關的信息;
只有在事件處理程序執行期間,event對象才會存在,一旦事件處理程序執行完畢,event對象就會被銷毀;
DOM中的事件對象
兼容DOM的瀏覽器會自動將一個事件對象event傳遞給事件處理程序
ps:關於事件對象中的this,target,currentTarget,看個例子:(注:event.target不支持IE瀏覽器,應該用event.srcElement;還有 IE中通過attachment添加的事件是運行在全局作用域中的,this===window)
當事件綁定在真正的目標元素上時,this===target===currentTarget ,而且綁定事件時是否捕獲結果都是一樣的,此時eventParse==2;
1 //this,target,currentTarget,this===currentTarget 2 $('#outer').on('click','#center',function(e){ 3 console.log(this.id);//on()中間的參數是個過濾器,相當於將事件綁定到了#center上;此時點擊#center將不會觸發事件 4 console.log(e.target.id); 5 console.log(e.currentTarget.id); 6 }); 7 8 $('#outer').on('click',function(e){ 9 console.log(this.id); 10 console.log(e.target.id); 11 console.log(e.currentTarget.id); 12 }); 13 14 event.stopPropagation()不能簡單說阻止了事件的冒泡,其實也阻止了事件的繼續捕獲,確切的說應該是阻止事件的進一步傳播 15 16 var d1 = document.getElementById('d1'); 17 d1.addEventListener('click', function (evt) { 18 console.log('d1'); 19 evt.stopPropagation(); 20 }, true); 21 var d2 = document.getElementById('d2'); 22 d2.addEventListener('click', function () { 23 console.log('d2'); 24 }, true);
輸出結果是:s1;
event.stopPropagation()可以組織事件的傳播,但它阻止不了綁定在該元素上的其他函數的執行,比如將上面例子改一下,給d1再綁定一個事件,同時d1的第一個事件中改成event.stopImmediatePropagation(),那么第二個事件也會被阻止,它不僅阻止事件的傳播還阻止后續事件的執行;
1 var d1 = document.getElementById('d1'); 2 d1.addEventListener('click', function (evt) { 3 console.log('d1'); 4 evt.stopImmediatePropagation(); 5 }, true); 6 d1.addEventListener('click', function (evt) { 7 console.log('d1+1'); 8 }, true); 9 var d2 = document.getElementById('d2'); 10 d2.addEventListener('click', function () { 11 console.log('d2'); 12 }, true);
IE中的事件對象
IE中event參數是未定的,事件對象是作為window的一個屬性存在的,因此可以通過window.event來訪問event對象,不同於DOM級中event是作為參數直接傳入和返回;
事件函數是以on開頭的;
屬性上也有一些不同,如下:

跨瀏覽器的事件對象
雖然DOM和IE中對象不同,但是兩者event中的全部信息和方法都是類似的只是實現方式不同,可以用前面提到過的EventUtil對象來求同存異
1 var EventUtil = { 2 addHandler: function (element, type, handler) { 3 if (element.addEventListener) { 4 element.addEventListener(type, handler, false); 5 } else if (element.attachEvent) { 6 element.attachEvent(on + type, handler); 7 } else { 8 element['on' + type] = handler; 9 } 10 }, 11 12 getEvent: function (event) { 13 return event ? event : window.event; 14 15 }, 16 17 getTarget: function (event) { 18 return event.target || event.srcElement; 19 }, 20 21 preventDefault: function (event) { 22 if (event.preventDefault) { 23 event.preventDefault(); 24 } else { 25 event.returnValue = false; 26 } 27 }, 28 29 stopPropagation: function (event) { 30 if (event.stopPropagation) { 31 event.stopPropagation(); 32 } else { 33 event.cancelBubble = true; 34 } 35 }, 36 37 removeHandler: function (element, type, handler) { 38 if (element.removeEventListener) { 39 element.removeEventListener(type, handler, false); 40 } else if (element.detachEvent) { 41 element.detachEvent(on + type, handler); 42 } else { 43 element['on' + type] = null 44 } 45 } 46 47 };
使用上面的EventUtil對象,舉個例子:
1 var myBtn=document.getElementById('my-btn'); 2 btn.onclick=function(event){ 3 event=EventUtil.getEvent(event); 4 EventUtil.preventDefault(event); 5 };
個例子:必須要假設有一個事件對象event傳入了事件處理程序中,要使用EventUtil中的方法需要將該事件對象傳給那些方法,該事件對象則需要通過其getEvent方法來獲得;
四、事件委托
每當將事件處理程序制定給元素時,運行中的瀏覽器代碼與支持頁面交互的JS代碼之間就會建立一個連接,而這種連接越多,頁面執行起來就越慢。考慮內存和性能問題,為了解決事件處理程序過多的問題,采用事件委托變得很有必要。(考慮到內存,也應該盡量減少不必要的事件處理程序,對於內存中過時不用的’空事件處理程序’,也是很有必要將其移除的;)
因為冒泡機制,比如既然點擊子元素,也會觸發父元素的點擊事件,那我們完全可以將子元素的事件要做的事寫到父元素的事件里,也就是將子元素的事件處理程序寫到父元素的事件處理程序中,這就是事件委托;利用事件委托,只指定一個事件處理程序,就可以管理某一個類型的所有事件;例如:
1 var myLinks=document.getElementById('my-links'); 2 myHandlers=function(event){ 3 event=EventUtil.getEvent(event); 4 var target=EventUtil.getTarget(event); 5 6 switch(target.id){ 7 case 'item-1': 8 location.href='http://www.cnblogs.com/lazychen/'; 9 break; 10 case 'item-2': 11 document.title='event'; 12 break; 13 case 'item-3': 14 console.log('hi'); 15 break; 16 } 17 }; 18 EventUtil.addHandler(myLinks,'click',myHandlers);
點擊任何一個 li ,該點擊事件依然會冒泡到父元素 ul 上,所以直接將點擊 li 后要做的事寫到了父元素的點擊事件里;
《JS高設 3rd》中列出了幾種最適合采用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress
總結一下js委托相關的:
- 因為把事件綁定到了父節點上,因此省了綁定事件。就算后面新增的子節點也有了相關事件,刪除部分子節點不用去銷毀對應節點上綁定的事件
- 父節點是通過event.target來找對應的子節點的。(事件處理程序中的this值始終等於currentTarget的值,指向的是綁定到的那個元素)
ps:文中盜了兩張圖~大大莫怪莫怪