當我們試圖綁定一些事件到DOM元素上的時候,我相信上面這4個方法是最常用的。而它們之間到底有什么不同呢?在什么場合下用什么方法是最有效的呢?
准備知識:
當我們在開始的時候,有些知識是必須具備的:
DOM樹
下圖僅僅是一個示例,這是一個在browser環境下的一棵模擬DOM樹,在下面的代碼中僅起到演示的作用:
Event bubbling (aka event propagation)冒泡
我們的頁面可以理解為一棵DOM樹,當我們在葉子結點上做什么事情的時候(如click一個a元素),如果我們不人為的設置stopPropagation(Moder Browser), cancelBubble(IE),那么它的所有父元素,祖宗元素都會受之影響,它們上面綁定的事件也會產生作用。看一個示例:
$('a').bind('click', function() { alert("That tickles!") });
當我們在a 上面點擊的時候,首先會觸發它本身所綁定的click事件,然后會一路往上,觸發它的父元素,祖先元素上所有綁定的click事件,就像下圖演示的那樣。
示例HTML
為了對下面的代碼進行演示,添加一些HTML代碼:
<ul id="members" data-role="listview" data-filter="true"> <!-- ... more list items ... --> <li> <a href="detail.html?id=10"> <h3>John Resig(jQuery的作者)</h3> <p><strong>jQuery Core Lead</strong></p> <p>Boston, United States</p> </a> </li> <!-- ... more list items ... --> </ul>
Bind():
.bind()是最直接的綁定方法 ,會綁定事件類型和處理函數到DOM element上, 這個方法是存在最久的,而且也很好的解決了瀏覽器在事件處理中的兼容問題。但是這個方法有一些performance方面的問題,看下面的代碼:
/* The .bind() method attaches the event handler directly to the DOM element in question ( "#members li a" ). The .click() method is just a shorthand way to write the .bind() method. */ $( "#members li a" ).bind( "click", function( e ) {} ); $( "#members li a" ).click( function( e ) {} );
上面的兩行代碼所完成的任務都是一致的,就是把event handler加到全部的匹配的<a>元素上。這里存在着一些效率方面的問題,一方面,我們隱式地把click handler加到所有的a標簽上,這個過程是昂貴的;另一方面在執行的時候也是一種浪費,因為它們都是做了同一件事卻被執行了一次又一次(比如我們可以把它hook到它們的父元素上,通過冒泡可以對它們中的每一個進行區分,然后再執行這個event handler)。
優點:
- 這個方法提供了一種在各種瀏覽器之間對事件處理的兼容性解決方案
- 非常方便簡單的綁定事件到元素上
- .click(), .hover()...這些非常方便的事件綁定,都是bind的一種簡化處理方式
- 對於利用ID選出來的元素是非常好的,不僅僅是很快的可以hook上去(因為一個頁面只有一個id),而且當事件發生時,handler可以立即被執行(相對於后面的live, delegate)實現方式
缺點:
- 它會綁定事件到所有的選出來的元素上
- 它不會綁定到在它執行完后動態添加的那些元素上
- 當元素很多時,會出現效率問題
- 當頁面加載完的時候,你才可以進行bind(),所以可能產生效率問題
.live()
.live()方法用到了事件委托的概念來處理事件的綁定。它和用.bind()來綁定事件是一樣的。.live()方法會綁定相應的事件到你所選擇的元素的根元素上,即是document元素上。那么所有通過冒泡上來的事件都可以用這個相同的handler來處理了。它的處理機制是這樣的,一旦事件冒泡到document上,jQuery將會查找selector/event metadata,然后決定那個handler應該被調用。jquery 1.8.2的源碼:

if ( delegateCount && !(event.button && event.type === "click") ) { for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764) if ( cur.disabled !== true || event.type !== "click" ) { selMatch = {}; matches = []; for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; sel = handleObj.selector; if ( selMatch[ sel ] === undefined ) { selMatch[ sel ] = handleObj.needsContext ? jQuery( sel, this ).index( cur ) >= 0 : jQuery.find( sel, this, null, [ cur ] ).length; } if ( selMatch[ sel ] ) { matches.push( handleObj ); } } if ( matches.length ) { handlerQueue.push({ elem: cur, matches: matches }); } } } }
當handler在執行的時候,因為有冒泡的參與,確實會有一些延遲,但是綁定的時候是特別的快。

/* The .live() method attaches the event handler to the root level document along with the associated selector and event information ( "#members li a" & "click" ) */ $( "#members li a" ).live( "click", function( e ) {} );
上面的code在和.bind()相比的時候有一個好處就是我們不需要在每個元素上再去綁定事件,而只在document上綁定一次就可以了。盡管這個不是最快的方式,但是確實是最少浪費的。
優點:
- 這里僅有一次的事件綁定,綁定到document上而不像.bind()那樣給所有的元素挨個綁定
- 那些動態添加的elemtns依然可以觸發那些早先綁定的事件,因為事件真正的綁定是在document上
- 你可以在document ready之前就可以綁定那些需要的事件
缺點:
- 從1.7開始已經不被推薦了,所以你也要開始逐步淘汰它了。
- Chaining沒有被正確的支持
- 當使用event.stopPropagation()是沒用的,因為都要到達document
- 因為所有的selector/event都被綁定到document, 所以當我們使用matchSelector方法來選出那個事件被調用時,會非常慢
- 當發生事件的元素在你的DOM樹中很深的時候,會有performance問題
.Delegate()
.delegate()有點像.live(),不同於.live()的地方在於,它不會把所有的event全部綁定到document,而是由你決定把它放在哪兒。而和.live()相同的地方在於都是用event delegation.

/* The .delegate() method behaves in a similar fashion to the .live() method, but instead of attaching the event handler to the document, you can choose where it is anchored ( "#members" ). The selector and event information ( "li a" & "click" ) will be attached to the "#members" element. */ $( "#members" ).delegate( "li a", "click", function( e ) {} );
優點:
- 你可以選擇你把這個事件放到那個元素上了
- chaining被正確的支持了
- jQuery仍然需要迭代查找所有的selector/event data來決定那個子元素來匹配,但是因為你可以決定放在那個根元素上,所以可以有效的減小你所要查找的元素。
- 可以用在動態添加的元素上
缺點:
- 需要查找那個那個元素上發生了那個事件了,盡管比document少很多了,不過,你還是得浪費時間來查找。
.On()
其實.bind(), .live(), .delegate()都是通過.on()來實現的,.unbind(), .die(), .undelegate(),也是一樣的都是通過.off()來實現的,這是1.8.2的源碼:

bind: function( types, data, fn ) { return this.on( types, null, data, fn ); }, unbind: function( types, fn ) { return this.off( types, null, fn ); }, live: function( types, data, fn ) { jQuery( this.context ).on( types, this.selector, data, fn ); return this; }, die: function( types, fn ) { jQuery( this.context ).off( types, this.selector || "**", fn ); return this; }, delegate: function( selector, types, data, fn ) { return this.on( types, selector, data, fn ); }, undelegate: function( selector, types, fn ) { // ( namespace ) or ( selector, types [, fn] ) return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); },
看一下,我們用如何用.on()來改寫前面通過 .bind(), .live(), .delegate()所注冊的事件:
/* The jQuery .bind(), .live(), and .delegate() methods are just one line pass throughs to the new jQuery 1.8.2 .on() method */ // Bind $( "#members li a" ).on( "click", function( e ) {} ); $( "#members li a" ).bind( "click", function( e ) {} ); // Live $( document ).on( "click", "#members li a", function( e ) {} ); $( "#members li a" ).live( "click", function( e ) {} ); // Delegate $( "#members" ).on( "click", "li a", function( e ) {} ); $( "#members" ).delegate( "li a", "click", function( e ) {} );
優點:
- 提供了一種統一綁定事件的方法
- 仍然提供了.delegate()的優點,當然如果需要你也可以直接用.bind()
缺點:
- 也許會對你產生一些困擾,因為它隱藏了一前面我們所介紹的三種方法的細節。
結論:
- 用.bind()的代價是非常大的,它會把相同的一個事件處理程序hook到所有匹配的DOM元素上
- 不要再用.live()了,它已經不再被推薦了,而且還有許多問題
- .delegate()會提供很好的方法來提高效率,同時我們可以添加一事件處理方法到動態添加的元素上。
- 我們可以用.on()來代替上述的3種方法
參考資料:
http://www.elijahmanor.com/2012/02/differences-between-jquery-bind-vs-live.html
http://www.alfajango.com/blog/the-difference-between-jquerys-bind-live-and-delegate/