1. 程式人生 > >jQuery 2.0.3 原始碼分析 事件繫結

jQuery 2.0.3 原始碼分析 事件繫結

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

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

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

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

.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)。但是依需注意的是,這樣會禁止元素上的複製/貼上功能,並且當點選元素時,會導致該元素被高亮顯示。
  • 在事件處理中呼叫 來阻止事件處理被新增到 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
  • 加大管理複雜。
  • 不好模擬使用者觸發事件

如何取捨就看專案實際中運用了。