初步理解JS的事件機制

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

 

 
假如咱們點擊一個div, 其實是先點擊document,而後點擊事件傳遞到div,並且並不會在這個div就停下,div有子元素就還會向下傳遞,最後又會冒泡傳遞迴document,如上圖
 
爲了兼容更多的瀏覽器,非特殊狀況通常咱們都是把事件添加到在事件冒泡階段。
 
2、事件處理程序
 
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); 
 
在同一個對象上註冊事件,並不必定按照註冊順序執行,冒泡或捕獲模式會影響其被觸發的順序;
 
3、事件對象
 
兼容觸發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方法來得到;
 
4、事件委託
 
每當將事件處理程序制定給元素時,運行中的瀏覽器代碼與支持頁面交互的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:文中盜了兩張圖~大大莫怪莫怪瀏覽器

相關文章
相關標籤/搜索