jQuery 2.0.3 源碼分析 事件綁定 - bind/live/delegate/on


事件(Event)是JavaScript應用跳動的心臟,通過使用JavaScript ,你可以監聽特定事件的發生,並規定讓某些事件發生以對這些事件做出響應

事件的基礎就不重復講解了,本來是定位源碼分析實現的, 所以需要有一定的基礎才行

為了下一步更好的理解內部的實現,所以首先得清楚的認識到事件接口的划分

網上資料遍地都是,但是作為一個jQuery系列的源碼分析,我還是很有必要在重新總結一下

jQuery對事件的綁定分別有幾個API http://www.css88.com/jqapi-1.9/

.bind()

.live()

.delegate()

.on()

不管是用什么方式綁定,歸根到底還是用addEventListener/attachEvent處理的,正如選擇器一樣不管如何匹配最終還是那么幾個瀏覽器接口處理

既然如此,事件為什么還要區分那么多不同的處理方案?

這里就要涉及到DOM事件處理模型了,就是常提到的捕獲冒泡

 


傳統的事件處理:

給某一元素綁定了一個點擊事件,傳入一個回調句柄處理

element.addEventListener('click',doSomething,false)

這樣的代碼再正常不過了

但是,如果頁面上有幾個百元素需要綁定(假設),那么務必就要綁定幾百次啦.

這樣問題就出現了:

第一:大量的事件綁定,性能消耗,而且還需要解綁(IE會泄漏)

第二:綁定的元素必須要存在

第三: 后期生成HTML會沒有事件綁定,需要重新綁定

第四: 語法過於繁雜

…………

有沒有辦法優化呢? 答案是肯定的

 


事件委托

DOM有個事件流的特性,也就是說我們在頁面上觸發節點的時候事件都會上下或者向上傳播,事件捕捉和事件冒泡。

借個圖:

DOM2.0模型將事件處理流程分為三個階段:一、事件捕獲階段,二、事件目標階段,三、事件起泡階段。

事件傳送可以分為3個階段。

(1).在事件捕捉(Capturing)階段,事件將沿着DOM樹向下轉送,目標節點的每一個祖先節點,直至目標節點。例如,若用戶單擊了一個超鏈接,則該單擊事件將從document節點轉送到html元素,body元素以及包含該鏈接的p元素。在此過程中,瀏覽器都會檢測針對該事件的捕捉事件監聽器,並且運行這件事件監聽器。

(2)在目標(target)階段,瀏覽器在查找到已經指定給目標事件的事件監聽器之后,就會運行 該事件監聽器。目標節點就是觸發事件的DOM節點。例如,如果用戶單擊一個超鏈接,那么該鏈接就是目標節點(此時的目標節點實際上是超鏈接內的文本節點)。

(3).在冒泡(Bubbling)階段,事件將沿着DOM樹向上轉送,再次逐個訪問目標元素的祖先節點到document節點。該過程中的每一步。瀏覽器都將檢測那些不是捕捉事件監聽器的事件監聽器,並執行它們。

利用事件傳播(這里是冒泡)這個機制,就可以實現事件委托。

具體來說,事件委托就是事件目標自身不處理事件,而是把處理任務委托給其父元素或者祖先元素,甚至根元素(document)

 


jQuery的事件優化

這么好的特性jQuery當然不會放過,所以就衍生出  .bind()、.live() .on()和.delegate()

jQuery的事件綁定有多個方法可以調用,以click事件來舉例:

  • click方法
  • bind方法
  • delegate方法
  • on方法

這里要清楚的認識:不管你用的是(click / bind / delegate)之中那個方法,最終都是jQuery底層都是調用on方法來完成最終的事件綁定。

因此從某種角度來講除了在書寫的方便程度及習慣上挑選,不如直接都采用on方法來的痛快和直接

所以在新版的API中都這么寫到:

.on()方法事件處理程序到當前選定的jQuery對象中的元素。在jQuery 1.7中,.on()方法 提供綁定事件處理的所有功能

 


性能對比

我們來個對直觀的測試

生成999個DOM節點,不做任何處理,內存消耗2.2M

image

 


給每一個元素綁定click事件,增加到5.6M

$('.ul a').click(function(e){
        alert('click event');
    });

image

 


委托事件2.2M

$('.ul').on('click', 'a', function(e){
        alert('click event');
    });

image

 

效果不言而喻了,除了性能的差異,通過委托的事件還能很友好的支持動態綁定

只要on的delegate對象是HTML頁面原有的元素,由於是事件的觸發是通過Javascript的事件冒泡機制來監測,所以對於所有子元素(包括后期通過JS生成的元素)所有的事件監測均能有效,且由於不用對多個元素進行事件綁定,能夠有效的節省內存的損耗。

 


.bind()

.bind()方法用於直接附加一個事件處理程序到元素上。

處理程序附加到jQuery對象中當前選中的元素,所以,在.bind()綁定事件的時候,這些元素必須已經存在

很明顯就是直接調用的,沒利用委托機制

 


.live()

將委托的事件處理程序附加到一個頁面的document元素,從而簡化了在頁面上動態添加的內容上事件處理的使用。

例如:

$('a').live('click', function() { alert("!!!") });

JQuery把alert函數綁定到$(document)元素上,並使用’click’和’a’作為參數。

任何時候只要有事件冒泡到document節點上,它就查看該事件是否是一個click事件,以及該事件的目標元素與’a’這一CSS選擇器是否匹配,如果都是的話,則執行函數。

 

因為更高版本的jQuery提供了更好的方法,沒有.live()方法的缺點,所以.live()方法不再推薦使用

特別是,使用.live()出現的以下問題:

  • 在調用 .live() 方法之前,jQuery 會先獲取與指定的選擇器匹配的元素,這一點對於大型文檔來說是很花費時間的。
  • 不支持鏈式寫法。例如,$("a").find(".offsite, .external").live( ... ); 這樣的寫法是不合法的,並不能像期待的那樣起作用。
  • 由於所有的 .live() 事件被添加到 document 元素上,所以在事件被處理之前,可能會通過最長最慢的那條路徑之后才能被觸發。
  • 在移動 iOS (iPhone, iPad 和 iPod Touch) 上,對於大多數元素而言,click 事件不會冒泡到文檔 body 上,並且如果不滿足如下情況之一,就不能和 .live() 方法一起使用:
    1. 使用原生的可被點擊的元素,例如, abutton,因為這兩個元素可以冒泡到 document
    2. document.body 內的元素使用 .on().delegate() 進行綁定,因為移動 iOS 只有在 body 內才能進行冒泡。
    3. 需要 click 冒泡到元素上才能應用的 CSS 樣式 cursor:pointer (或者是父元素包含document.documentElement)。但是依需注意的是,這樣會禁止元素上的復制/粘貼功能,並且當點擊元素時,會導致該元素被高亮顯示。
  • 在事件處理中調用 event.stopPropagation() 來阻止事件處理被添加到 document 之后的節點中,是效率很低的。因為事件已經被傳播到 document 上。
  • .live() 方法與其它事件方法的相互影響是會令人感到驚訝的。例如,$(document).unbind("click") 會移除所有通過 .live() 添加的 click 事件!

 


.delegate()

為了突破單一.bind()方法的局限性,實現事件委托,jQuery 1.3引入了.live()方法。后來,為解決“事件傳播鏈”過長的問題,jQuery 1.4又支持為.live()方法指定上下文對象。而為了解決無謂生成元素集合的問題,jQuery 1.4.2干脆直接引入了一個新方法.delegate()。

使用.delegate(),前面的例子可以這樣寫:

$('#element).delegate('a', 'click', function() { alert("!!!") });

JQuery掃描文檔查找$(‘#element’),並使用click事件和’a’這一CSS選擇器作為參數把alert函數綁定到$(‘#element)上。

任何時候只要有事件冒泡到$(‘#element)上,它就查看該事件是否是click事件,以及該事件的目標元素是否與CCS選擇器相匹配。如果兩種檢查的結果都為真的話,它就執行函數。

可以注意到,這一過程與.live()類似,但是其把處理程序綁定到具體的元素而非document這一根上

那么 $('a').live() == $(document).delegate('a') ?

可見,.delegate()方法是一個相對完美的解決方案。但在DOM結構簡單的情況下,也可以使用.live()。

 


.on()

其實.bind(), .live(), .delegate()都是通過.on()來實現的,.unbind(), .die(), .undelegate(),也是一樣的都是通過.off()來實現的

提供了一種統一綁定事件的方法

 


總結:

在下列情況下,應該使用.live()或.delegate(),而不能使用.bind():

  • 為DOM中的很多元素綁定相同事件;
  • 為DOM中尚不存在的元素綁定事件;

用.bind()的代價是非常大的,它會把相同的一個事件處理程序hook到所有匹配的DOM元素上
不要再用.live()了,它已經不再被推薦了,而且還有許多問題
.delegate()會提供很好的方法來提高效率,同時我們可以添加一事件處理方法到動態添加的元素上
我們可以用.on()來代替上述的3種方法

不足點也是有的:

  • 並非所有的事件都能冒泡,如load, change, submit, focus, blur
  • 加大管理復雜。
  • 不好模擬用戶觸發事件

如何取舍就看項目實際中運用了。


免責聲明!

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



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