Javascript事件模型系列(三)jQuery中的事件監聽方式及異同點


  作為全球最知名的js框架之一,jQuery的火熱程度堪稱無與倫比,簡單易學的API再加豐富的插件,幾乎是每個前端程序員的必修課。從讀《鋒利的jQuery》開始,到現在使用jQuery有一年多的時間了,對jQuery算是比較了解了,唯一沒做到的就是讀源碼。網上看到有人寫jQuery源碼解析的,我也沒細看,個人覺得如果光是為了解析源碼而解析源碼,未免有點太勞神了,沒有實際用途,我更傾向於在實際應用中遇到不懂的方法或是文檔說明不清楚的地方,可以查找到相應的位置看下源碼,足矣。

  閑話不多講了,今天的主題是jQuery中的事件監聽器的綁定方式。在學習jQuery之初,就在網上不只一次搜過相關主題,看了幾篇被抄來抄去的文章,算是了解了內情。今天去搜網上的文章依舊有很多,所以我就在思考我這篇文章該寫成什么樣子,既能表達清楚主題而又與其他的文章不同。思考良久我決定,從jQuery源碼的角度結合各種例子來把整個來龍去脈說個透徹,讓讀者看過這篇文章后就再也不需看別人的,理想有點豐滿哈~好像又是閑話了,速度奔主題。。。

jQuery中的四種事件監聽方式

  jQuery中提供了四種事件監聽方式,分別是bind、live、delegate、on,對應的解除監聽的函數分別是unbind、die、undelegate、off。在開始看他們之前,先來聲明一個例子,各函數的用法將圍繞這個例子進行,html代碼如下:

<ol id="myol">
    <li>列表元素1</li>
    <li>列表元素2</li>
    <li>列表元素3</li>
    <li>列表元素4</li>
</ol>

  同時再聲明一個函數,用來作為監聽函數,JS代碼如下:

function getHtml(){
    alert(this.innerHTML);
}

  看完例子大家應該明白想要干什么了,沒錯,就是實現點擊每個列表元素的時候,把它的內部html彈出來,灰常簡單~

  忍不了了,奔主題奔主題!下面來分別看一下這四種方式:

bind(type,[data],function(eventObject))

  在我初學jQuery的時候,這個函數用的是最多的(基本上就認它),作用就是在選擇到的元素上綁定特定事件類型的監聽函數,參數的含義如下:

  type:事件類型,如click、change、mouseover等;

  data:傳入監聽函數的參數,通過event.data取到。可選;

  function:監聽函數,可傳入event對象,這里的event是jQuery封裝的event對象,與原生的event對象有區別,使用時需要注意。

  來看看bind的源碼:

bind: function( types, data, fn ) {
        return this.on( types, null, data, fn );
    }

  可以看到內部是調用了on方法,這個on是什么樣的呢?稍后我們再看。先用我們上面的例子來試試:

$('#myol li').bind('click',getHtml);
  1. 列表元素1
  2. 列表元素2
  3. 列表元素3
  4. 列表元素4

  bind的特點就是會把監聽器綁定到目標元素上,有一個綁一個,在頁面上的元素不會動態添加的時候使用它沒什么問題。但如果列表中動態增加一個“列表元素5”,點擊它是沒有反應的,必須再bind一次才行。要想不這么麻煩,我們可以使用live。jQuery還有一種事件綁定的簡寫方式如a.click(function(){});、a.change(function(){});等,它們的作用與bind一樣,僅僅是簡寫而已。

live(type, [data], fn)

  參數與bind一樣,源碼是怎樣的呢?

live: function( types, data, fn ) {
        jQuery( this.context ).on( types, this.selector, data, fn );
        return this;
    }

  可以看到live方法並沒有將監聽器綁定到自己(this)身上,而是綁定到了this.context上了。這個context是什么東西呢?其實就是元素的限定范圍,看了下面的代碼就清楚了:

$('#myol li').context; //document
$('#myol li','#myol').context; //document
$('#myol li',$('#myol')[0]); //ol 

  通常情況下,我們都不會像第三種方式那樣使用選擇器,所以也就認為這個context通常就是document了,即live方法把監聽器綁定到了document上了。不把監聽器直接綁定在元素上,你是不是想起事件委托機制來了呢?若沒有,可以點擊這里回憶一下。live正是利用了事件委托機制來完成事件的監聽處理,把節點的處理委托給了document。在監聽函數中,我們可以用event.currentTarget來獲取到當前捕捉到事件的節點。下面的例子來揭曉:

$('#myol li').live('click',getHtml);
  1. 列表元素1
  2. 列表元素2
  3. 列表元素3
  4. 列表元素4

 

  使用事件委托的優點一目了然,新添加的元素不必再綁定一次監聽器。看來live這貨還真不錯,以后拋棄bind就用它了!可以嗎?答案是否定的,而且是大大的否定。因為將監聽器綁定到了document上,所以事件的處理得等待層層冒泡,直到冒泡到根節點才開始處理,在DOM樹較深或者節點的嵌套關系很復雜時,會有意想不到的結果,根節點的負擔太重了。就像四世同堂、五世同堂,甚至八世同堂(現實中不太可能,但在HTML中層級關系可能遠比這還多),老爺子肯定記不清哪個孫子是哪個兒子的,哪個重孫又是哪個兒子的兒子的,老爺子腦子一亂,糊塗了,事情就辦錯了。為此,jQuery官方已宣布在1.7版本開始廢棄live,改用其他方式代替。所以我們也順應號召,罷用此方法。

  正因為live存在那樣的缺點,所以我們就思考,既然老爺子負擔那么重,可不可以別把監聽器綁定在document上呢,綁定在就近的父級元素上不就好了。順應正常邏輯,delegate誕生了。

delegate(selector,type,[data],fn)

  參數多了一個selector,用來指定觸發事件的目標元素,監聽器將被綁定在調用此方法的元素上。看看源碼:

delegate: function( selector, types, data, fn ) {
        return this.on( types, selector, data, fn );
    }

  又是調用了on,並且把selector傳給了on。看來這個on真的是舉足輕重的東西。照樣先不管它。看看示例先:

$('#myol').delegate('li','click',getHtml);
  1. 列表元素1
  2. 列表元素2
  3. 列表元素3
  4. 列表元素4

  我們在例子中將監聽器綁定到ol上,event.currentTarget顯示當前捕獲到事件的元素是ol。這下,我們的選擇又多了一些靈活性,不單可以利用事件委托,還可以選擇委托的對象。畢竟老麻煩同一個人幫忙很不好嘛。對於如何選擇委托對象,還是需要一定的策略的,畢竟父級元素可以有很多。我覺得原則應該是選擇最近的“穩定”元素,選擇最近是因為事件可以更快的冒泡上去,能夠在第一時間進行處理。所謂“穩定”是指該父級元素是一開始就在頁面上的,不是動態添加上來的,而且將來也不會消失掉,這樣可以保證它可以時時監控着自己的孩子。

  看了這么多,你是不是迫不及待想看看這個on的真實面目了呢,這就來:

on(type,[selector],[data],fn)

  參數與delegate差不多但還是有細微的差別,首先type與selector換位置了,其次selector變為了可選項。交換位置的原因不好查證,應該是為了讓視覺上更舒服一些吧。至於selector為什么是可選了呢,速度回想。。。對了,bind也調用了它,因為bind是綁定在了自己身上,所以只能傳個null進來。對嗎?不對,傳null和不傳是兩回事啊,差點掉坑里。看看on的源碼里能不能找到線索呢,打開源碼只發現一句關鍵的:

return this.each( function() {
            jQuery.event.add( this, types, fn, data, selector );
        })

  在on的內部,又調用了event.add方法,納尼?順着再走一步好了,進入event.add查看,越加復雜,不過還好,看到了一段熟悉的代碼:

if ( elem.addEventListener ) {
     elem.addEventListener( type, eventHandle, false );
     } else if ( elem.attachEvent ) {
         elem.attachEvent( "on" + type, eventHandle );
        }

  看來已經開始進行事件監聽,不會再往深走了,舒一口氣繼續往下看:

// Add to the element's handler list, delegates in front
if ( selector ) {
     handlers.splice( handlers.delegateCount++, 0, handleObj );
     } else {
       handlers.push( handleObj );
       }

  傳不傳selector的區別就在此處了!但是!區別到底是什么啊,智商捉急!翻來覆去就是看不明白。哎,本來想通過源碼把事情說明白的,可惜在下不才,還是獻丑了。暫時還是說不明白了。等待日后更新此處吧。

         不過,解釋不明白源碼還是可以通過例子來理解嘛!我們先不傳selector來看看:

$('#myol li').on('click',getHtml);
  1. 列表元素1
  2. 列表元素2
  3. 列表元素3
  4. 列表元素4

  可以看到event.currentTarget是li自己,與bind的效果一樣。我為什么一直要糾結這個傳不傳selector的區別呢?老老實實傳進去不就完了。其實是因為之前有看過文章提到如何用on來代替bind和live的寫法,其中代替bind的寫法就是不傳selector進去,今日就想探清楚這個究竟。至於傳selector進去,就是跟delegate一樣的意義了,除了參數順序不同,其他完全一樣。

  終於看到on的真實作用了,那么,這么多的事件綁定方式,我們該如何進行選擇呢?

bind、live、delegate、on如何選擇?

  其實這個問題是完全不必糾結的,因為你已經知道他們之間的區別了不是么?根據實際情況斟酌使用就行。不過官方有一個推薦就是盡量使用on,因為其他方法都是內部調用on來完成的,直接使用on可以提高效率,而且你完全可以用on來代替其他三種寫法。至於如何代替我想就不必這么直白的寫出來了,真正理解它們的區別之后自然而然也就不是難事了。

總結一下

  jQuery的事件監聽方式就分析完了,除了后面看event.add源碼有點水分之外,應該是能把整個原理解釋明白了。若對事件的冒泡和委托機制有不懂的,可以看下這篇文章《事件的捕獲-冒泡機制及事件委托機制》 有助於理解。能不能達到我最開始的理想呢?還請讀者評價吧,歡迎指點!


免責聲明!

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



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