初步理解JS的事件機制


一、事件流(捕獲,冒泡)
 
事件流:指從頁面中接收事件的順序,有冒泡流和捕獲流。
當頁面中發生某種事件(比如鼠標點擊,鼠標滑過等)時,毫無疑問子元素和父元素都會接收到該事件,可具體順序是怎樣的呢?冒泡和捕獲則描述了兩種不同的順序。
 
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及以前只支持事件冒泡;
 
例子:
 
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:文中盜了兩張圖~大大莫怪莫怪

  
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM