JS中的事件、事件冒泡和事件捕獲、事件委託 JavaScript事件模型及事件代理

http://www.javashuo.com/article/p-caellqno-h.htmljavascript

http://www.javashuo.com/article/p-wyebpsvr-e.htmlhtml

JavaScript事件模型及事件代理

事件模型

  JavaScript事件使得網頁具有互動和交互性,咱們應該對其深刻了解以便開發工做,在各式各樣的瀏覽器中,JavaScript事件模型主要分爲3種:原始事件模型、DOM2事件模型、IE事件模型。java

  1.原始事件模型(DOM0級)

    這是一種被全部瀏覽器都支持的事件模型,對於原始事件而言,沒有事件流,事件一旦發生將立刻進行處理,有兩種方式能夠實現原始事件:node

      (1)在html代碼中直接指定屬性值:<button id="demo" type="button" onclick="doSomeTing()" />  web

      (2)在js代碼中爲 document.getElementsById("demo").onclick = doSomeTing()ajax

    優勢:全部瀏覽器都兼容chrome

    缺點:1)邏輯與顯示沒有分離;2)相同事件的監聽函數只能綁定一個,後綁定的會覆蓋掉前面的,如:a.onclick = func1; a.onclick = func2;將只會執行func2中的內容。3)沒法經過事件的冒泡、委託等機制(後面會講到)完成更多事情。瀏覽器

    由於這些缺點,雖然原始事件類型兼容全部瀏覽器,但仍不推薦使用。性能優化

  2.DOM2事件模型

    此模型是W3C制定的標準模型,現代瀏覽器(IE6~8除外)都已經遵循這個規範。W3C制定的事件模型中,一次事件的發生包含三個過程:app

    (1).事件捕獲階段,(2).事件目標階段,(3).事件冒泡階段。以下圖所示

 

  事件捕獲:當某個元素觸發某個事件(如onclick),頂層對象document就會發出一個事件流,隨着DOM樹的節點向目標元素節點流去,直到到達事件真正發生的目標元素。在這個過程當中,事件相應的監聽函數是不會被觸發的。

  事件目標:當到達目標元素以後,執行目標元素該事件相應的處理函數。若是沒有綁定監聽函數,那就不執行。

  事件冒泡:從目標元素開始,往頂層元素傳播。途中若是有節點綁定了相應的事件處理函數,這些函數都會被一次觸發。

  IE 5.5: div -> body -> document 

  IE 6.0: div -> body -> html -> document 

  Mozilla 1.0: div -> body -> html -> document -> window 

  全部的事件類型都會經歷事件捕獲可是隻有部分事件會經歷事件冒泡階段,例如submit事件就不會被冒泡。 

  事件的傳播是能夠阻止的:
  • 在W3c中,使用stopPropagation()方法
  • 在IE下設置eve.cancelBubble = true;
  在捕獲的過程當中stopPropagation();後,後面的冒泡過程就不會發生了。

  標準的事件監聽器該如何綁定:

    addEventListener("eventType","handler","true|false");其中eventType指事件類型,注意不要加‘on’前綴,與IE下不一樣。第二個參數是處理函數,第三個即用來指定是否在捕獲階段進行處理,通常設爲false來與IE保持一致(默認設置),除非你有特殊的邏輯需求。監聽器的解除也相似:removeEventListner("eventType","handler","true!false");

  3.IE事件模型

  IE不把該對象傳入事件處理函數,因爲在任意時刻只會存在一個事件,因此IE把它做爲全局對象window的一個屬性,爲求證其真僞,使用IE8執行代碼alert(window.event),結果彈出是null,說明該屬性已經定義,只是值爲null(與undefined不一樣)。難道這個全局對象的屬性是在監聽函數裏才加的?因而執行下面代碼:

    window.onload = function (){alert(window.event);}

    setTimeout(function(){alert(window.event);},2000);

    結果第一次彈出【object event】,兩秒後彈出依然是null。因而可知IE是將event對象在處理函數中設爲window的屬性,一旦函數執行結束,便被置爲null了。IE的事件模型只有兩步,先執行元素的監聽函數,而後事件沿着父節點一直冒泡到document。冒泡已經講解過了,這裏不重複。IE模型下的事件監聽方式也挺獨特,綁定監聽函數的方法是:attachEvent( "eventType","handler"),其中evetType爲事件的類型,如onclick,注意要加’on’。解除事件監聽器的方法是 detachEvent("eventType","handler" )

    IE的事件模型已經能夠解決原始模型的三個缺點,但其本身的缺點就是兼容性,只有IE系列瀏覽器才能夠這樣寫。

   以上就是3種事件模型,在咱們寫代碼的時候,爲了兼容ie,一般使用如下寫法:

    var demo = document.getElementById('demo');

    if(demo.attachEvent){

     demo.attachEvent('onclick',func);

    }else{

     demo.addEventListener('click',func,false);

    }

event詳解

  上面已經講解了3種事件模型,事件,大部分狀況下指的是用戶的鼠標動做和鍵盤動做,如點擊、移動鼠標、按下某個鍵,爲何說大部分呢,由於事件不僅僅只有這兩部分,還有其餘的例如document的load和unloaded。那麼事件在瀏覽器中,到底包含哪些信息呢?

  事件被封裝成一個event對象,包含了該事件發生時的全部相關信息(event的屬性)以及能夠對事件進行的操做(event的方法)。

  我爲下圖中的button綁定了一個點擊事件,而後將event輸出到控制檯:

 

  能夠看到是一個MouseEvent對象,包含了一系列屬性,如鼠標點擊的位置等。那麼敲擊鍵盤時產生的event對象和它同樣嗎?看看就知道: 

  能夠看到是一個KeyboardEvent對象,屬性跟上面的也不太同樣,如沒有clientX/Y(敲鍵盤怎麼能獲取到鼠標的位置呢)。無論是MouseEvent仍是KeyboardEvent或是其餘類型,都是繼承自一個叫Event的類。

  event對象經常使用屬性、方法:

  1. 事件定位相關屬性

  若是你細細看了MouseEvent對象裏的屬性,必定發現了有不少帶X/Y的屬性,它們都和事件的位置相關。具體包括:x/y、clientX/clientY、pageX/pageY、screenX/screenY、layerX/layerY、offsetX/offsetY 六對。爲何有這麼多X-Y啊?不要着急,做爲一個web開發者,你應該瞭解各瀏覽器之間是有差別的,這些屬性都有各自的意思:

    x/y與clientX/clientY值同樣,表示距瀏覽器可視區域(工具欄除外區域)左/上的距離;

    pageX/pageY,距頁面左/上的距離,它與clientX/clientY的區別是不隨滾動條的位置變化;

    screenX/screenY,距計算機顯示器左/上的距離,拖動你的瀏覽器窗口位置能夠看到變化;

    layerX/layerY與offsetX/offsetY值同樣,表示距有定位屬性的父元素左/上的距離。

 

 下面列出了各屬性的瀏覽器支持狀況。(+支持,-不支持)    

offsetX/offsetY W3C-  IE+  Firefox- Opera+  Safari+ chrome+
x/y W3C-  IE+  Firefox- Opera+  Safari+ chrome+
layerX/layerY W3C-  IE-  Firefox+ Opera-  Safari+ chrome+
pageX/pageY W3C-  IE+- Firefox+ Opera+  Safari+ chrome+
clientX/clientY W3C+  IE+  Firefox+ Opera+  Safari+ chrome+
screenX/screenY W3C+  IE+  Firefox+ Opera+  Safari+ chrome+

        注意:該表摘自其餘文章,我未作所有驗證,可是最新版本的現代瀏覽器,這些屬性貌似是都支持了,爲了更好的兼容性,一般選擇W3C支持的屬性。

  2.其餘經常使用屬性

    target:發生事件的節點;

    currentTarget:當前正在處理的事件的節點,在事件捕獲或冒泡階段;

      timeStamp:事件發生的時間,時間戳。

    bubbles:事件是否冒泡。

    cancelable:事件是否能夠用preventDefault()方法來取消默認的動做;

    keyCode:按下的鍵的值;

  3. event對象的方法

    event. preventDefault()//阻止元素默認的行爲,如連接的跳轉、表單的提交;

    event. stopPropagation()//阻止事件冒泡

    event.initEvent()//初始化新事件對象的屬性,自定義事件會用,不經常使用

    event. stopImmediatePropagation()//能夠阻止掉同一事件的其餘優先級較低的偵聽器的處理(這貨表示沒用過,優先級就不說明了,谷歌或者問度娘吧。)

    4. 阻止事件的默認行爲,例如click <a>後的跳轉~ 

  • 在W3c中,使用preventDefault()方法; 
  • 在IE下設置window.event.returnValue = false; 

  event.target與event.currentTarget他們有什麼不一樣?

  target在事件流的目標階段;currentTarget在事件流的捕獲,目標及冒泡階段。只有當事件流處在目標階段的時候,兩個的指向纔是同樣的, 而當處於捕獲和冒泡階段的時候,target指向被單擊的對象而currentTarget指向當前事件活動的對象(通常爲父級)。

  

事件觸發器

  前面提到的事件都是依靠用戶或者瀏覽器自帶事件去觸發的,好比click是用戶點擊事件目標觸發,load是指定元素已載入的時候瀏覽器的行爲事件,等等,若是隻有在這些條件下才能觸發事件,那麼咱們的自定義事件如何觸發呢?

  事件觸發器就是用來觸發某個元素下的某個事件,固然也能夠用來觸發自定義事件IE下fireEvent方法,現代瀏覽器(chrome,firefox等)有dispatchEvent方法。

  

 

  這裏先介紹下自定義事件(事件模擬):

自定義事件

  想要實現一個自定義事件,須要通過下面幾步:

  1.createEvent(eventType)

    事件被封裝成一個event對象,這在上面已經說過了,咱們想要自定義一個事件,js中有這麼一個方法createEvent(eventType),見名知義,顯然是用於「創造」一個事件的,沒錯,要想自定義事件,首先,咱們得「創造」一個事件。

     參數:eventType 共5種類型:
       Events :包括全部的事件. 
           HTMLEvents:包括 'abort', 'blur', 'change', 'error', 'focus', 'load', 'reset', 'resize', 'scroll', 'select', 
                                    'submit', 'unload'. 事件
           UIEevents :包括 'DOMActivate', 'DOMFocusIn', 'DOMFocusOut', 'keydown', 'keypress', 'keyup'.
                                  間接包含 MouseEvents. 
           MouseEvents:包括 'click', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup'. 
           MutationEvents:包括 'DOMAttrModified', 'DOMNodeInserted', 'DOMNodeRemoved', 
                                      'DOMCharacterDataModified', 'DOMNodeInsertedIntoDocument', 
                                      'DOMNodeRemovedFromDocument', 'DOMSubtreeModified'.     

  2. 在createEvent後必須初始化,爲你們介紹5種對應的初始化方法
      HTMLEvents 和 通用 Events:
                 initEvent( 'type', bubbles, cancelable )
        UIEvents:
                      initUIEvent( 'type', bubbles, cancelable, windowObject, detail )
       MouseEvents: 
                      initMouseEvent( 'type', bubbles, cancelable, windowObject, detail, screenX, screenY, 
                      clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget )
       MutationEvents :
                      initMutationEvent( 'type', bubbles, cancelable, relatedNode, prevValue, newValue, 
                      attrName, attrChange ) 

    這裏重點介紹MouseEvents(鼠標事件模擬):

    鼠標事件能夠經過建立一個鼠標事件對象來模擬(mouse event object),而且授予他一些相關信息,建立一個鼠標事件經過傳給createEvent()方法一個字符串「MouseEvents」,來建立鼠標事件對象,以後經過iniMouseEvent()方法來初始化返回的事件對象,iniMouseEvent()方法接受15參數,參數以下:
     type string類型 :要觸發的事件類型,例如‘click’。
     bubbles Boolean類型:表示事件是否應該冒泡,針對鼠標事件模擬,該值應該被設置爲true。
     cancelable bool類型:表示該事件是否可以被取消,針對鼠標事件模擬,該值應該被設置爲true。
          view 抽象視圖:事件授予的視圖,這個值幾乎全是document.defaultView.
      detail int類型:附加的事件信息這個初始化時通常應該默認爲0。
      screenX int類型 : 事件距離屏幕左邊的X座標
      screenY int類型 : 事件距離屏幕上邊的y座標
      clientX int類型 : 事件距離可視區域左邊的X座標
      clientY int類型 : 事件距離可視區域上邊的y座標
      ctrlKey Boolean類型 : 表明ctrol鍵是否被按下,默認爲false。
      altKey Boolean類型 : 表明alt鍵是否被按下,默認爲false。
      shiftKey Boolean類型 : 表明shif鍵是否被按下,默認爲false。
      metaKey Boolean類型: 表明meta key 是否被按下,默認是false。
      button int類型: 表示被按下的鼠標鍵,默認是零. 
      relatedTarget (object) : 事件的關聯對象.只有在模擬mouseover 和 mouseout時用到。

    若是你想要了解更多事件模擬參數詳解,請查看這篇文章,http://www.cnblogs.com/MrBackKom/archive/2012/06/26/2564501.html。或者查看《javascript高級程序設計》的模擬事件章節

  3. 在初始化完成後就能夠隨時觸發須要的事件了,爲你們介紹targetObj.dispatchEvent(event)使targetObj對象的event事件觸發。(IE上請用fireEvent方法)
    4. 例子
      //例子1  當即觸發鼠標被按下事件

1
2
3
4
var  fireOnThis = document.getElementById( 'demo' );
var  evObj = document.createEvent( 'MouseEvents' );
evObj.initMouseEvent(  'click' true true , window, 1, 12, 345, 7, 220,  false false true false , 0,  null  );
fireOnThis.dispatchEvent(evObj);

    //例子2  考慮兼容性的一個鼠標移動事件

1
2
3
4
5
6
7
8
9
var  fireOnThis = document.getElementById( 'someID' );
if ( document.createEvent ) {
      var  evObj = document.createEvent( 'MouseEvents' );
      evObj.initEvent(  'mousemove' true false  );
      fireOnThis.dispatchEvent(evObj);
  } else  if ( document.createEventObject )
  {
      fireOnThis.fireEvent( 'onmousemove' );
  }

  

  事件代理

  傳統的事件處理中,咱們爲每個須要觸發事件的元素添加事件處理器,可是這種方法將可能會致使內存泄露或者性能降低(特別是經過ajax獲取數據後重復綁定事件,總之,越頻繁風險越大)。事件代理在js中是一個很是有用但對初學者稍難理解的功能,咱們將事件處理器綁定到一個父級元素上,避免了頻繁的綁定多個子級元素,依靠事件冒泡機制與事件捕獲機制,子級元素的事件將委託給父級元素。事件冒泡與捕獲在上面事件模型中已經講解過。

  有了事件捕獲和冒泡的認識後,下面舉例說明事件代理:

  假設咱們有一個列表,列表中的每個li和li中的span都須要綁定某個事件處理函數。以下代碼:

 

1
2
3
4
5
6
7
8
<ul id= "parent-ul" >
    <li><span>Item 1</span></li>
    <li><span>Item 2</span></li>
     <li><span>Item 3</span></li>
     <li><span>Item 4</span></li>
     <li><span>Item 5</span></li>
     <li><span>Item 6</span></li>
</ul>

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
~( function () {
     var  ParentNode = document.querySelector( "#parent-ul" );
     var  targetNodes = ParentNode.querySelectorAll( "li" );
     var  spanNodes = ParentNode.querySelectorAll( "span" );
      
     //綁定事件處理函數
     for ( var  i=0, l = targetNodes.length; i < l; i++){
         addListenersToLi(targetNodes[i]);
     }
     for ( var  i=0, l = spanNodes.length; i < l; i++){
         addListenersToSpan(spanNodes[i]);
     }
 
     //事件處理函數
     function  addListenersToLi(targetNode){
         targetNode.onclick =  function  targetClick(e){
             if (e.target && e.target.nodeName.toUpperCase() ==  "LI" ) {
                 console.log( "當你看見個人時候,LI點擊事件已經生效!" );
             }
         };
     }
     function  addListenersToSpan(targetNode){
         targetNode.onclick =  function  targetClick(e){
             if (e.target && e.target.nodeName.toUpperCase() ==  "SPAN" ) {
                 console.log( "當你看見個人時候,SPAN點擊已經生效!" );
             }
         };
     }
})();

  

  

  這裏爲li和span元素都添加了onclick事件處理函數,可是若是這些li和span有可能被刪除或者新增,那麼老是須要爲新增的li、span元素從新綁定事件,這種寫法使得咱們很苦惱,除了開頭提到的問題外,增長了代碼量並且代碼看上去不太整潔了,那麼使用事件代理會怎麼樣呢?以下代碼:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
~( function () {
     // 獲取li的父節點,併爲其添加一個click事件
     document.getElementById( "parent-ul" ).addEventListener( "click" , function (e) {
         // 檢查事件源e.targe是否爲span
         if (e.target && e.target.nodeName.toUpperCase() ==  "SPAN" ) {
             // 真正的處理過程在這裏
             console.log( "當你看見個人時候,SPAN事件代理已經生效!" );
         }<br>      //檢查事件源e.target是否爲li
         if (e.target && e.target.nodeName.toUpperCase() ==  "LI" ) {
             // 真正的處理過程在這裏
             console.log( "當你看見個人時候,LI事件代理已經生效!" );
         }
     });
})();

  咱們改變了思路,爲li、span的父級元素即id爲parent-ul的ul元素添加了一click事件,當點擊事件發生時,咱們能夠經過e.target捕獲事件目標,並經過e.target.nodeName.toUpperCase== "LI"來判斷事件目標是否爲li(span同理),如是那麼執行相應的事件處理程序。使用這樣的方式有利於解決前面提到的一些問題:

  1.最直接的就是,代碼更整潔了,並且可讀性更強。
  2.對於動態化的頁面(如本例,li、span會新增和刪除),不用頻繁的綁定事件,減小了內存泄露的機率。

 注意:不是全部的事件都能冒泡的。blur、focus、load和unload不能像其它事件同樣冒泡。事實上blur和focus能夠用事件捕獲而非事件冒泡的方法得到(在IE以外的其它瀏覽器中)。 
    在管理鼠標事件的時候有些須要注意的地方。若是你的代碼處理mousemove事件的話你趕上性能瓶頸的風險可就大了,由於mousemove事件觸發很是頻繁。而mouseout則由於其怪異的表現而變得很難用事件代理來管理。 

 jq事件代理:jq爲提供了delegate()函數處理事件代理,這裏很少介紹,我的在工做中使用on()函數解決一些事件代理問題(使用更方便),解決上訴例子的代碼以下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
~( function () {
       $( "#parent-ul" ).on( "click" , "li,span" , function (e) {           
         if ($( this )[0].nodeName== "SPAN" ) {
               // 真正的處理過程在這裏
               console.log( "當你看見個人時候,SPAN事件代理已經生效!" );
               //這裏要阻止冒泡,否則點擊span時會觸發li的事件
               e.stopPropagation();
            }
             
            if ($( this )[0].nodeName== "LI" ) {
                 // 真正的處理過程在這裏
                 console.log( "當你看見個人時候,LI事件代理已經生效!" );
             }
       });
  })();   

  若是你使用jq,推薦使用on()方法。

 
      

概述:

那什麼叫事件委託呢?它還有一個名字叫事件代理,JavaScript高級程序設計上講:事件委託就是利用事件冒泡,只指定一個事件處理程序,就能夠管理某一類型的全部事件。那這是什麼意思呢?網上的各位大牛們講事件委託基本上都用了同一個例子,就是取快遞來解釋這個現象,我仔細揣摩了一下,這個例子還真是恰當,我就不去想別的例子來解釋了,借花獻佛,我摘過來,你們認真領會一下事件委託究竟是一個什麼原理:

有三個同事預計會在週一收到快遞。爲簽收快遞,有兩種辦法:一是三我的在公司門口等快遞;二是委託給前臺MM代爲簽收。現實當中,咱們大都採用委託的方案(公司也不會容忍那麼多員工站在門口就爲了等快遞)。前臺MM收到快遞後,她會判斷收件人是誰,而後按照收件人的要求籤收,甚至代爲付款。這種方案還有一個優點,那就是即便公司裏來了新員工(無論多少),前臺MM也會在收到寄給新員工的快遞後覈實並代爲簽收。

這裏其實還有2層意思的:

第一,如今委託前臺的同事是能夠代爲簽收的,即程序中的現有的dom節點是有事件的;

第二,新員工也是能夠被前臺MM代爲簽收的,即程序中新添加的dom節點也是有事件的。

爲何要用事件委託:

通常來講,dom須要有事件處理程序,咱們都會直接給它設事件處理程序就行了,那若是是不少的dom須要添加事件處理呢?好比咱們有100個li,每一個li都有相同的click點擊事件,可能咱們會用for循環的方法,來遍歷全部的li,而後給它們添加事件,那這麼作會存在什麼影響呢?

在JavaScript中,添加到頁面上的事件處理程序數量將直接關係到頁面的總體運行性能,由於須要不斷的與dom節點進行交互,訪問dom的次數越多,引發瀏覽器重繪與重排的次數也就越多,就會延長整個頁面的交互就緒時間,這就是爲何性能優化的主要思想之一就是減小DOM操做的緣由;若是要用事件委託,就會將全部的操做放到js程序裏面,與dom的操做就只須要交互一次,這樣就能大大的減小與dom的交互次數,提升性能;

每一個函數都是一個對象,是對象就會佔用內存,對象越多,內存佔用率就越大,天然性能就越差了(內存不夠用,是硬傷,哈哈),好比上面的100個li,就要佔用100個內存空間,若是是1000個,10000個呢,那隻能說呵呵了,若是用事件委託,那麼咱們就能夠只對它的父級(若是隻有一個父級)這一個對象進行操做,這樣咱們就須要一個內存空間就夠了,是否是省了不少,天然性能就會更好。

事件委託的原理:

事件委託是利用事件的冒泡原理來實現的,何爲事件冒泡呢?就是事件從最深的節點開始,而後逐步向上傳播事件,舉個例子:頁面上有這麼一個節點樹,div>ul>li>a;好比給最裏面的a加一個click點擊事件,那麼這個事件就會一層一層的往外執行,執行順序a>li>ul>div,有這樣一個機制,那麼咱們給最外面的div加點擊事件,那麼裏面的ul,li,a作點擊事件的時候,都會冒泡到最外層的div上,因此都會觸發,這就是事件委託,委託它們父級代爲執行事件。

事件委託怎麼實現:

終於到了本文的核心部分了,哈哈,在介紹事件委託的方法以前,咱們先來看一段通常方法的例子:

子節點實現相同的功能:

?
1
2
3
4
5
6
< ul id = "ul1" >
   < li >111</ li >
   < li >222</ li >
   < li >333</ li >
   < li >444</ li >
</ ul >

實現功能是點擊li,彈出123:

?
1
2
3
4
5
6
7
8
9
window.onload = function (){
   var oUl = document.getElementById( "ul1" );
   var aLi = oUl.getElementsByTagName( 'li' );
   for ( var i=0;i<aLi.length;i++){
     aLi[i].onclick = function (){
       alert(123);
     }
   }
}

上面的代碼的意思很簡單,相信不少人都是這麼實現的,咱們看看有多少次的dom操做,首先要找到ul,而後遍歷li,而後點擊li的時候,又要找一次目標的li的位置,才能執行最後的操做,每次點擊都要找一次li;

那麼咱們用事件委託的方式作又會怎麼樣呢?

?
1
2
3
4
5
6
window.onload = function (){
   var oUl = document.getElementById( "ul1" );
   oUl.onclick = function (){
     alert(123);
   }
}

這裏用父級ul作事件處理,當li被點擊時,因爲冒泡原理,事件就會冒泡到ul上,由於ul上有點擊事件,因此事件就會觸發,固然,這裏當點擊ul的時候,也是會觸發的,那麼問題就來了,若是我想讓事件代理的效果跟直接給節點的事件效果同樣怎麼辦,好比說只有點擊li纔會觸發,不怕,咱們有絕招:

Event對象提供了一個屬性叫target,能夠返回事件的目標節點,咱們成爲事件源,也就是說,target就能夠表示爲當前的事件操做的dom,可是不是真正操做dom,固然,這個是有兼容性的,標準瀏覽器用ev.target,IE瀏覽器用event.srcElement,此時只是獲取了當前節點的位置,並不知道是什麼節點名稱,這裏咱們用nodeName來獲取具體是什麼標籤名,這個返回的是一個大寫的,咱們須要轉成小寫再作比較(習慣問題):

?
1
2
3
4
5
6
7
8
9
10
11
window.onload = function (){
              var oUl = document.getElementById( "ul1" );
             oUl.onclick = function (ev){
                  var ev = ev || window.event;
                      var target = ev.target || ev.srcElement;
                  if (target.nodeName.toLowerCase() == 'li' ){
                         alert(123);
                 alert(target.innerHTML);
                     }
             }
         }

這樣改下就只有點擊li會觸發事件了,且每次只執行一次dom操做,若是li數量不少的話,將大大減小dom的操做,優化的性能可想而知!

上面的例子是說li操做的是一樣的效果,要是每一個li被點擊的效果都不同,那麼用事件委託還有用嗎?

?
1
2
3
4
5
6
< div id = "box" >
     < input type = "button" id = "add" value = "添加" />
     < input type = "button" id = "remove" value = "刪除" />
     < input type = "button" id = "move" value = "移動" />
     < input type = "button" id = "select" value = "選擇" />
   </ div >
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
window.onload = function (){
       var Add = document.getElementById( "add" );
       var Remove = document.getElementById( "remove" );
       var Move = document.getElementById( "move" );
       var Select = document.getElementById( "select" );
       
       Add.onclick = function (){
         alert( '添加' );
       };
       Remove.onclick = function (){
         alert( '刪除' );
       };
       Move.onclick = function (){
         alert( '移動' );
       };
       Select.onclick = function (){
         alert( '選擇' );
       }
       
     }

上面實現的效果我就很少說了,很簡單,4個按鈕,點擊每個作不一樣的操做,那麼至少須要4次dom操做,若是用事件委託,能進行優化嗎?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
window.onload = function (){
       var oBox = document.getElementById( "box" );
       oBox.onclick = function (ev) {
         var ev = ev || window.event;
         var target = ev.target || ev.srcElement;
         if (target.nodeName.toLocaleLowerCase() == 'input' ){
           switch (target.id){
             case 'add' :
               alert( '添加' );
               break ;
             case 'remove' :
               alert( '刪除' );
               break ;
             case 'move' :
               alert( '移動' );
               break ;
             case 'select' :
               alert( '選擇' );
               break ;
           }
         }
       }
       
     }

用事件委託就能夠只用一次dom操做就能完成全部的效果,比上面的性能確定是要好一些的

如今講的都是document加載完成的現有dom節點下的操做,那麼若是是新增的節點,新增的節點會有事件嗎?也就是說,一個新員工來了,他能收到快遞嗎?

看一下正常的添加節點的方法:

?
1
2
3
4
5
6
7
< input type = "button" name = "" id = "btn" value = "添加" />
   < ul id = "ul1" >
     < li >111</ li >
     < li >222</ li >
     < li >333</ li >
     < li >444</ li >
   </ ul >

如今是移入li,li變紅,移出li,li變白,這麼一個效果,而後點擊按鈕,能夠向ul中添加一個li子節點

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
window.onload = function (){
       var oBtn = document.getElementById( "btn" );
       var oUl = document.getElementById( "ul1" );
       var aLi = oUl.getElementsByTagName( 'li' );
       var num = 4;
       
       //鼠標移入變紅,移出變白
       for ( var i=0; i<aLi.length;i++){
         aLi[i].onmouseover = function (){
           this .style.background = 'red' ;
         };
         aLi[i].onmouseout = function (){
           this .style.background = '#fff' ;
         }
       }
       //添加新節點
       oBtn.onclick = function (){
         num++;
         var oLi = document.createElement( 'li' );
         oLi.innerHTML = 111*num;
         oUl.appendChild(oLi);
       };
     }

這是通常的作法,可是你會發現,新增的li是沒有事件的,說明添加子節點的時候,事件沒有一塊兒添加進去,這不是咱們想要的結果,那怎麼作呢?通常的解決方案會是這樣,將for循環用一個函數包起來,命名爲mHover,以下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
window.onload = function (){
       var oBtn = document.getElementById( "btn" );
       var oUl = document.getElementById( "ul1" );
       var aLi = oUl.getElementsByTagName( 'li' );
       var num = 4;
       
       function mHover () {
         //鼠標移入變紅,移出變白
         for ( var i=0; i<aLi.length;i++){
           aLi[i].onmouseover = function (){
             this .style.background = 'red' ;
           };
           aLi[i].onmouseout = function (){
             this .style.background = '#fff' ;
           }
         }
       }
       mHover ();
       //添加新節點
       oBtn.onclick = function (){
         num++;
         var oLi = document.createElement( 'li' );
         oLi.innerHTML = 111*num;
         oUl.appendChild(oLi);
         mHover ();
       };
     }

雖然功能實現了,看着還挺好,但實際上無疑是又增長了一個dom操做,在優化性能方面是不可取的,那麼有事件委託的方式,能作到優化嗎?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
window.onload = function (){
       var oBtn = document.getElementById( "btn" );
       var oUl = document.getElementById( "ul1" );
       var aLi = oUl.getElementsByTagName( 'li' );
       var num = 4;
       
       //事件委託,添加的子元素也有事件
       oUl.onmouseover = function (ev){
         var ev = ev || window.event;
         var target = ev.target || ev.srcElement;
         if (target.nodeName.toLowerCase() == 'li' ){
           target.style.background = "red" ;
         }
         
       };
       oUl.onmouseout = function (ev){
         var ev = ev || window.event;
         var target = ev.target || ev.srcElement;
         if (target.nodeName.toLowerCase() == 'li' ){
           target.style.background = "#fff" ;
         }
         
       };
       
       //添加新節點
       oBtn.onclick = function (){
         num++;
         var oLi = document.createElement( 'li' );
         oLi.innerHTML = 111*num;
         oUl.appendChild(oLi);
       };
     }

看,上面是用事件委託的方式,新添加的子元素是帶有事件效果的,咱們能夠發現,當用事件委託的時候,根本就不須要去遍歷元素的子節點,只須要給父級元素添加事件就行了,其餘的都是在js裏面的執行,這樣能夠大大的減小dom操做,這纔是事件委託的精髓所在。

--------------------------------------------------華麗的分割線-------------- -----------------------------------------------------------------------------------------------------

在這裏先感謝一下@蒼茫大地NV 的提問,提的問題很是好!👏👏👏

他的問題是:

如今給一個場景 ul > li > div > p,div佔滿li,p佔滿div,仍是給ul綁定時間,須要判斷點擊的是否是li(假設li裏面的結構是不固定的),那麼e.target就多是p,也有多是div,這種狀況你會怎麼處理呢?

那咱們如今就再現一下他給的場景

?
1
2
3
4
5
6
7
8
9
10
11
12
13
< ul id = "test" >
     < li >
       < p >11111111111</ p >
     </ li >
     < li >
       < div >
       </ div >
     </ li >
     < li >
       < span >3333333333</ span >
     </ li >
     < li >4444444</ li >
   </ ul >

如上列表,有4個li,裏面的內容各不相同,點擊li,event對象確定是當前點擊的對象,怎麼指定到li上,下面我直接給解決方案:

?
1
2
3
4
5
6
7
8
9
10
11
var oUl = document.getElementById( 'test' );
   oUl.addEventListener( 'click' , function (ev){
     var target = ev.target;
     while (target !== oUl ){
       if (target.tagName.toLowerCase() == 'li' ){
         console.log( 'li click~' );
         break ;
       }
       target = target.parentNode;
     }
   })

核心代碼是while循環部分,實際上就是一個遞歸調用,你也能夠寫成一個函數,用遞歸的方法來調用,同時用到冒泡的原理,從裏往外冒泡,知道currentTarget爲止,噹噹前的target是li的時候,就能夠執行對應的事件了,而後終止循環,恩,沒毛病!

這裏看不到效果,你們能夠複製過去運行一下!

--------------------------------------------------------------------華麗的分割線----------------------------------------------------------------

總結:

那什麼樣的事件能夠用事件委託,什麼樣的事件不能夠用呢?

適合用事件委託的事件:click,mousedown,mouseup,keydown,keyup,keypress。

值得注意的是,mouseover和mouseout雖然也有事件冒泡,可是處理它們的時候須要特別的注意,由於須要常常計算它們的位置,處理起來不太容易。

不適合的就有不少了,舉個例子,mousemove,每次都要計算它的位置,很是很差把控,在不如說focus,blur之類的,自己就沒用冒泡的特性,天然就不能用事件委託了。

相關文章
相關標籤/搜索