前幾章已經把最核心的實現都分解過了,這一章我們看看jQuery是如何實現事件模擬的
在Internet Explorer 8和更低,一些事件change
和 submit
本身不冒泡,但jQuery修改這些冒泡,創建一致的跨瀏覽器的行為。
焦點事件
blur :
在這個事件觸發前,元素已經失去焦點,不冒泡,同步觸發。target 指向當前失去焦點的元素。
focus:
在這個事件觸發前,元素已經得到焦點,不冒泡,同步觸發。target 指向當前得到焦點的元素。
與此同時DOM Level 3 事件模塊 還定義了 focusin ,focusout 以及 DOMFocusIn ,DOMFocusOut 四個事件。
focusin :
在當前元素獲得焦點前以及相關元素失去焦點前觸發,可冒泡,同步觸發。target 指向當前將要獲得焦點的元素,relatedTarget 指向失去焦點的元素
focusout :
在當前失去焦點前觸發,可冒泡,同步觸發。target 指向當前將要失去焦點的元素,relatedTarget 指向將要失去焦點的元素。
DOMFocusIn :
在這個事件觸發前,元素已經得到焦點,可冒泡,同步觸發。target 指向當前得到焦點的元素。
DOMFocusOut :
在這個事件觸發前,元素已經沒有焦點,可冒泡,同步觸發。target 指向當前失去焦點的元素
事件的兼容性支持
1, 所有 IE 版本均支持focusin/focusout事件(注意:IE6/7/8中不支持el.addEventListener方法)。
2, Opera 最強悍即支持attachEvent,又支持addEventListener。且這兩種方式添加事件均支持focusin/focusout事件。
3, Safari/Chrome 給人一個驚喜,雖然el.onfocusin方式不支持,但 addEventListener方式卻支持。因此想讓Safari/Chrome中支持focusin事件,只能使用addEventListener方式添加事件。
4, Firefox 任何一種添加事件方式都不支持 focusout/focuso
那么如何在所有的平台上都兼容focusin/focusout?
jQuery.event.special方法
這個方法在event.add,event.dispatch等幾個事件的處理地方都會被調用到,jQuert.event.special 對象用於某些事件類型的特殊行為和屬性
換句話說就是某些事件不是大眾化的的事件,不能一概處理,比如 load 事件擁有特殊的 noBubble 屬性,可以防止該事件的冒泡而引發一些錯誤
所以需要單獨針對處理,但是如果都寫成判斷的形式,顯然代碼結構就不合理了,而且不方便提供給用戶自定義擴展
在webkit下的截圖,特殊事件類型
大體上針對9種事件,不同情況下處理hack,我們具體分析下焦點事件兼容冒泡處理,處理大同小異
jQuery.event 事件機制 focusin/ focusout 事件
針對focusin/ focusout 事件jQuery.event.special擴充2組處理機制,
special.setup方法主要是來在Firefox中模擬focusin和focusout事件的,因為各大主流瀏覽器只有他不支持這兩個事件。
由於這兩個方法支持事件冒泡,所以可以用來進行事件代理
var attaches = 0, handler = function( event ) { jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); }; jQuery.event.special[ fix ] = { setup: function() { if ( attaches++ === 0 ) { document.addEventListener( orig, handler, true ); } }, teardown: function() { if ( --attaches === 0 ) { document.removeEventListener( orig, handler, true ); } } };
前面的分析我們就知道通過事件最終都是通過add方法綁定的,也就是addEventListener方法綁定的,但是在add方法之前會有一個過濾分支
以前看不懂代碼,現在回過來恍然大悟了,原來這個方法是這樣用的
所以最終代碼會跑到各種的Hack中了,
可見對focusin/ focusout 的處理,沒有用通用的方法,而且是直接用的special.setup中的綁定
幾個重點
1 綁定的是focusin/ focusout 事件,內部確換成了focus/blur事件
2 document.addEventListener( orig, handler, true );事件綁在document上,最后是true,用的捕獲綁定
3 jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );方法
為什么用捕獲?
因為火狐不支持focusin/ focusout事件,所以要找個所有瀏覽器都兼容類似事件,對了那就是focus/blur,
但是focus/blur不能冒泡丫,怎么辦?
咱不是還有捕獲嗎?
那么利用捕獲怎么模擬出冒泡呢?
jQuery.event.simulate方法
jQuery.event.simulate = function( type, elem, event, bubble ) { // 重寫事件 var e = jQuery.extend( new jQuery.Event(), event, { type: type, isSimulated: true, originalEvent: {} } ); // 如果要冒泡 if ( bubble ) { // 利用jQuery.event.trigger模擬觸發事件 jQuery.event.trigger( e, null, elem ); } else { // 否則利用jQuery.event.dispatch來執行處理 jQuery.event.dispatch.call( elem, e ); } // 如果需要阻止默認操作,則阻止 if ( e.isDefaultPrevented() ) { event.preventDefault(); } }
可以看到focusin/ focusout 可冒泡事件實現原理是
1 focusin 事件添加事件處理程序時,jQuery 會在 document 上會添加 handler 函數
2 在事件捕獲階段監視特定元素的 focus/ blur 動作,捕獲行為發生在 document 對象上,這樣才能有效地實現所有元素都能可以冒泡的事件。
3 程序監視到存在 focus/ blur 行為,就會觸發綁定在 document 元素上的事件處理程序,該事件處理程序在內部調用 simulate 邏輯觸發事件冒泡,以實現我們希望的可以冒泡事件。
4 之后利用jQuery.event.trigger模擬觸發事件,把從target-document的元素都過濾出來,分析每個節點上是否綁定了事件句柄,依次處理,按照一定的規范,比如是否有事件阻止之類的,這里就不再重復分析了jQuery.event.trigger http://www.cnblogs.com/aaronjs/p/3452279.html
總結
咋一看其實原理都挺簡單的, 但是jQuery為了實現兼容統一,可謂煞費苦心了,把事件冒泡與捕獲都統一模擬了一遍
- jQuery為統一原生Event對象而封裝的jQuery.Event類,封裝了preventDefault,stopPropagation,stopImmediatePropagation原生接口,可以直接捕獲到用戶的行為
- 由核心組件 jQuery.cache 實現注冊事件處理程序的存儲,實際上綁定在 DOM元素上的事件處理程序只有一個,即 jQuery.cache[elem[expando]].handle 中存儲的函數,該函數在內部調用 jQuery.event.dispatch(event) 實現對該DOM元素特定事件的緩存的訪問,並依次執行這些事件處理程序。
- jQuery.event.add(elem, types, handler, data, selector) 方法用於給特定elem元素添加特定的事件 types([type.namespace, type.namespace, ...])的事件處理程序 handler, 通過第四個參數 data 增強執行當前 handler 事件處理程序時的 $event.data 屬性,以提供更靈活的數據通訊,而第五個元素用於指定基於選擇器的委托事件
- namespace 命名空間機制,namespace 機制可以對事件進行更為精細的控制,開發人員可以指定特定空間的事件,刪除特定命名空間的事件,以及觸發特定命名空間的事件。這使得對事件處理機制的功能更加健
- jQuert.event.special 對象用於某些事件類型的特殊行為和屬性。比如 load 事件擁有特殊的 noBubble 屬性,可以防止該事件的冒泡而引發一些錯誤。總的來說,有這樣一些方法和屬性:
- jQuery.event.simulate(type, elem, event, bubble)模擬事件並立刻觸發方法,可用於在DOM元素 elem 上模擬自定義事件類型 type,參數 bubble用於指定該事件是否可冒泡,event 參數表示 jQuery 事件對象 $event。 模擬事件通過事件對象的isSimulated屬性為 true 表示這是模擬事件。該方法內部調用 trigger() 邏輯 或 dispatch() 邏輯立刻觸發該模擬事件。該方法主要用於修正瀏覽器事件的兼容性問題,比如模擬出可冒泡的 focusin/ focusout 事件,修正IE中 change 事件的不可冒泡問題,修正IE中 submit事件不可冒泡問題
- jQuery.event.dispatch(event) 方法在處理事件委托機制時,依賴委托節點在DOM樹的深度安排優先級,委托的DOM節點層次越深,其執行優先級越高。而其對於stopPropagation的處理有些特殊,在事件委托情況下並不一定會調用綁定在該DOM元素上的該類型的所有事件處理程序,而依賴於委托的事件處理程序的執行結果,如果低層委托的事件處理程序聲明了停止冒泡,那么高層委托的事件以及自身綁定事件就不會被執行,這拓展了 DOM 委托機制的功能。
- jQuery.event.trigger(event | type, data, elem, onlyHandlers) 方法提供開發人員以程序方式觸發特定事件的接口,該方法的第一個參數可以是 $event/ event 對象 ,也可以是某個事件類型的字符串 type; 第二個參數 data 用於擴展該事件觸發時事件處理程序的參數規模,用於傳遞一些必要的信息。 elem參數表示觸發該事件的DOM元素;最后該方法在默認情況下,其事件會冒泡,並且在有默認動作的情況下執行默認行為,但是如果指定了 onlyHandlers 參數,該方法只會觸發綁定在該DOM元素上的事件處理程序,而不會引發冒泡和默認動作,也不會觸發特殊的 trigger 行為。
- …………………