作者:nuysoft/高雲 QQ:47214707 EMail:nuysoft@gmail.com
讀讀寫寫,不對的地方請告訴我,多多交流共同進步
前記:
關於缺少樣本,首先感謝這位朋友的反饋。在讀源碼的過程中,必然要寫很多例子來跟蹤代碼的調用過程,經常還需要單步調試,遇到難度的地方,比如動畫、AJAX要反覆嘗試,盡量覆蓋各個分支,這個過程如果用文字記錄下來的話會很長,而且未必能講清楚,因為不像DEMO看效果就可以了,最好的辦法是錄成視頻,這個有點遙遠看時間和學的情況吧。
關於入門的樣本,有的朋友建議多做一些入門教程,我的建議是,去看官網,官網有很全的DEMO,詳細的解釋,其他的入門教程也是抄官網的,官網上的很多評論很有價值值得仔細看看
關於造輪子,有的人在分析的過程中,會嘗試用原生js去實現一個類似的功能,這是個好習慣;本系列的目的是深入的去分析jQuery的思想和技巧,如果理解透徹了模仿一個類似的自然水到渠成,我會在分析的過程將思路、用到的原生js都分解出來。重複造輪子是好事,而且必須要重複造,不重複造怎能明白原創的精髓,不重複造怎麼能創新,關鍵的關鍵在於:多造少用,造出來的輪子暫時讓它留在實驗室裡,就像我的籃球老師(同事)告訴我的,在平時練球時,要多用你不熟悉的那隻手,但是比賽時要用你擅長的那隻手,就是多練少用。直到你的雙手都嫻熟了,直到你的輪子達到原創的水平了,才能把它應用到項目中。
本章後續小節預告:
封裝事件對象 介面應用 介面調用鏈 源碼解析DOMReady專題
10. 事件處理
10.1 概述
做為JavaScript庫,首先要解決瀏覽器事件相容問題,正確的管理事件,並提供便捷的介面方便開發;jQuery的事件模型以簡單優雅的方式實現了這些需求:
n 自訂jQuery.Event對象,類比實現W3C標準的DOM 3層級事件模型,統一了事件的屬性和方法
n 可以在一個事件類型上添加多個事件處理函數,可以一次添加多個事件類型的事件處理函數
n 提供了常用事件的便捷方法,例如$( selector ).click( function …);同時支援通過代碼觸發事件,例如$( selector ).click( )
n 支援自訂事件
n 擴充了組合事件:toggle(click時順序執行綁定的事件)、hover(滑鼠移入、移除)
n 擴充了one(只執行一次)、live-die(延遲綁定)、delegate-undelegate(類似live)
n 提供了統一的事件封裝、綁定、執行、銷毀機制
n 最佳化DOM ready事件
事件的管理不外乎綁定、觸發、銷毀(詳見下一節基礎知識),jQuery則在瀏覽器原生的支援上進行了封裝和完善。
jQuery並沒有將事件處理函數直接綁定到DOM元素上,而是通過$.data儲存在緩衝$.cahce上;首先為DOM元素分配一個唯一ID,綁定的事件儲存在$.cahce[ 唯一ID ][ $.expand ][ 'events' ]上,而events是個鍵-值對應對象,鍵就是事件類型,對應的值就是由事件處理函數組成的數組;最後在DOM元素上綁定(addEventListener/ attachEvent)一個事件處理函數eventHandle,這個過程由jQuery.event.add 實現。
當事件觸發時eventHandle被執行,eventHandle再去$.cache中尋找曾經綁定的事件處理函數並執行,這個過程由jQuery.event. trigger 和jQuery.event.handle實現。
事件的銷毀則由jQuery.event.remove 實現,remove對緩衝$.cahce中儲存的事件數目組進行銷毀,當緩衝中的事件全部銷毀時,調用removeEventListener/ detachEvent銷毀綁定在DOM元素上的事件處理函數eventHandle。
10.2 基礎知識
在我們對jQuery的事件模型進行介紹和分析之前,如果讀者不熟悉瀏覽器的事件模型,建議先讀一讀《JavaScript權威指南》第17章 事件和事件處理。以下是關鍵內容的筆記:
l 0級事件模型(所有瀏覽器都支援的實際標準API)
n 方式一:作為JavaScript屬性的事件控制代碼(將函數直接綁定到事件屬性上)
elment.onclick = function(){
// ...
}
n 方式二:作為HTML 屬性的事件控制代碼(將函數控制代碼以字串的方式直接賦值給DOM元素的HTML 屬性)
<input type="button" value="0級事件模型" onclick="alert(this.nodeName);" />
l 2級事件模型(除Internet Explorer以外的所有現在瀏覽器都支援2級DOM事件模型)
n 事件傳播分三個階段進行:
u 捕捉階段,事件從Document對象沿著文檔樹向下傳播給目標節點。如果目標的任何一個祖先(不是目標自身)專門註冊了捕捉事件控制代碼,那麼在事件傳遞過程中,就會運行這些控制代碼。
u 目標階段,直接註冊在目標上的適合的事件控制代碼將運行。這與0級事件模型提供的事件處理方法相似
u 起泡階段,事件將從目標元素向上傳播回或起泡回Document對象的文檔層次。
雖然所有事件都受事件傳播的捕捉階段的支配,但並非所有類型的事件都起泡。一般來說,原始輸入事件起泡(大部分事件),而進階語義事件不起泡(blue focus load unload)
n 註冊一個事件控制代碼Element.addEventListener( String type, Function listener, boolean useCapture )
u type 事件監聽器調用所針對的時間類型。例如,load click mousedown
u listener 當指定類型的事件指派給這個元素的時候,事件監聽器函數被調用。調用的時候,這個監聽器函數被傳遞給一個Event對象,然後作為在該元素上註冊的一個方法來調用
u useCapture 如果為true,那麼只有在事件傳播的捕捉階段,指定的listener才會被調用。更常用的值是false,意味著在捕捉階段不會調用listener,而當該節點是實際的事件目標或事件從它的原始目標起泡到該節點時,調用listener。
該方法將指定的事件監聽器函數添加到當前節點的監聽器函數結合中,以處理指定類型type的事件。如果useCapture為true,則監聽器被註冊為捕捉階段監聽器。如果useCapture為false,它就註冊普通事件監聽器
addEventListener()可能被調用多次,在同一個節點上為同一種類型的事件註冊多個事件控制代碼。但要注意,DOM不能確定多個事件控制代碼被調用的順序。
如果一個事件監聽器函數在同一個節點上用相同的type和useCapture參數註冊了兩次,那麼第二次註冊將被忽略。如果正在處理一個節點上的事件時,在這個節點上註冊了一個新的事件監聽器,則不會為那個事件調用新的事件監聽器。
當用Node.cloneNode()方法或Document.importNode()方法複製一個Document節點時,不會複製為原始節點註冊的事件監聽器。
n 刪除一個事件控制代碼Element.removeEventListener( String type, Function listener, boolean useCapture )
u type 要刪除事件監聽器的事件類型
u listener 要刪除的事件監聽器函數
u useCapture 如果要刪除是捕捉事件監聽器,則為true;如果要刪除的是普通事件監聽器,則為false。
該方法將刪除指定的事件監聽器函數。參數type和useCapture必須與調用addEventListener()方法的相應參數一致。如果沒有找到與指定的參數匹配的事件監聽器,該方法什麼都不做。
如果一個事件監聽器函數被該方法刪除,那麼當節點發生指定類型的事件時,就不再調用它。即使一個事件監聽器被同一個節點上同類型事件註冊的另一個事件監聽器刪除,它也不再被調用。
n 停止事件傳播Event.stopPropagation()
n 阻止預設動作Event.preventDefault()
l IE事件模型
n 註冊一個事件控制代碼Element.attachEvent( String type, Function listener )
u type 事件監聽器調用所針對的事件的類型,帶有一個“on”首碼。例如,onload onclick onmousedown
u listener 當指定類型的事件指派給這個元素的時候,事件監聽器函數被調用。不會為這個函數傳遞任何參數,但是它可以從Window對象的event屬性獲得Event對象。
這個方法是一個特定與IE的事件註冊方法。它和標準的addEventListener()方法(IE不支援它)具有相同的作用,但是,它和該函數也有一些重要的差別:
u 由於IE事件模型不支援事件捕獲,所以attachEvent()和detachEvent()只需要兩個參數:事件類型和控制代碼函數
u 傳遞給IE方法的事件控制代碼名應該包含on首碼。例如,和attachEvent()一起使用的是onlick,而不是和addEventListener()一起使用的click
u 使用attachEvent()註冊的函數調用的時候沒有Event對象參數。相反,它們必須讀取Window對象的event屬性
u 使用attachEvent()註冊的函數作為全域函數調用,而不是作為事件發生其上的文件項目的方法調用。也就是說,當使用attachEvent()註冊的一個事件控制代碼執行的時候,this關鍵字引用的是Window對象,而不是事件目標元素。
u attachEvent()允許同一個事件控制代碼函數註冊多次。當制定類型的一個事件發生的時候,註冊的哈蘇調用次數與它註冊的次數相同
n 刪除一個事件監聽器Event.detachEvent( String type, Function listener )
u type 要刪除的事件監聽器所針對的事件的類型,帶有一個on首碼。例如onclick
u listener 要刪除事件監聽器函數
這個方法解除掉由attachEvent()方法所執行的事件控制代碼函數註冊。它是removeEventListener()方法的特定與IE的替代。要為一個元素刪除一個事件函數控制代碼,只需要使用你最初傳遞attachEvent()的相同參數來調用detachEvent()
n 停止事件傳播window.event.cancelBubule = true;
n 阻止預設動作window.event.returnValue = false