事件系統是一個框架很是重要的部分,用於響應用戶的各類行爲。
瀏覽器提供了3個層次的api,用於響應用戶的各類行爲。css
1.最原始的是寫在元素標籤內。
2.再次是腳本內,以el.onXXX = function綁定的方式,統稱爲DOM0事件系統。
3.最後是多投事件系統,一個元素的同一類型事件能夠綁定多個回調,統常稱爲DOM2事件系統。html
因爲瀏覽器大戰,現存兩套API。node
IE與opera:
綁定事件:el.attachEvent("on"+ type, callback)
卸載事件:el.detachEvent("on"+ type. callback)
建立事件:el.document.createEventObject()
派發事件:el.fireEvent(type,event)jquery
w3c:git
綁定事件:el.addEventListener(type,callback,[phase])
卸載事件:el.removeEventListener(type,callback,[phase])
建立事件:el.createEvent(types)
初始化事件:event.initEvent()
派發事件:el.dispatchEvent(event)github
從api的數量與形式來看,w3c提供的複雜不少,相對於也強大不少,下面咱們將逐一分析web
首先咱們先來幾個簡單的例子,不必動用框架。不過事實上,整個事件系統就創建在它們的基礎上。chrome
function addEvent (el, callback, useCapture) { if(el.dispatchEvent){//w3c優先 el.addEventListener(type, callback, !!useCapture ); } else { el.attachEvent( "on"+ type, callback ); } return callback; //返回callback方便卸載時用 } function removeEvent (el, type, callback, useCapture) { if (el.dispatchEvent) { //w3c優先 el.removeEventListener (type, callback, !!useCapture); } else { el.detachEvent( "on"+type, callback ) } } function fireEvent (el, type, args, event) { args = args || {} if (el.dispatchEvent) { //w3c優先 event = document.createEvent("HTMLEvents"); event.initEvent(type, true, true); } else { event = document.createEventObject(); } for (var i in args) if (args.hasOwnProperty(i)) { event[i] = args[i] } if (el.dispatchEvent) { el.dispatchEvent(event); } else { el.fireEvent('on'+type , event) } }
一,onXXX綁定方式的缺陷編程
onXXX既能夠寫在html標籤內,也能夠獨立出來,做爲元素節點的一個特殊屬性來處理,不過做爲一個古老的綁定方式,它很難預料到人們對這方面的擴展。bootstrap
總結下來有如下不足:
1.onXXX對DOM3新增的事件或FF某些私有實現沒法支持,主要有如下事件:
DOMActivate
DOMAttrModified
DOMAttributeNameChanged
DOMCharacterDataModified
DOMContentLoaded
DOMElementNameChanged
DOMFocusIn
DOMFocusOut
DOMMouseScroll
DOMNodeInserted
DOMNodeInsertedIntoDocument
DOMNodeRemoved
DOMNodeRemovedFromDcouemnt
DOMSubtreeModified
MozMousePixelScroll
2.onXXX只容許元素每次綁定一個回調,重複綁定沖掉以前的綁定
3.onXXX在IE下回調沒有參數,在其餘瀏覽器回調的第一個參數是事件對象。
4.onXXX只能在冒泡階段可用。
二,attachEvent的缺陷
attachEvent是微軟在IE5添加的API,Opera也支持,也對於onXXX方式,它能夠容許同一種元素同一種事件綁定多個回調,也就是所謂多投事件機制。但帶來的麻煩只多很多,存在如下幾點缺陷。
1.ie下只支持微軟的事件系統,DOM3事件一律不支持。
2.IE下attchEvent回調中的this不是指向被綁定元素,而是window!
3.IE下同種事件綁定多個回調時,回調並非按照綁定時的順序依次觸發的!
4.IE下event事件對象與w3c的存在太多差別了,有的沒法對上號,好比currentTarget
5.IE仍是隻支持冒泡階段。
關於事件對象,w3c是大勢所趨,在IE9支持W3c那一套API時,這對咱們實現事件代理很是有幫助。
三,addEventListener的缺陷
w3c這一套API也不是至善至美,畢竟標準老是滯後於現實,剩下的標準瀏覽器各有本身的算盤,它們之間也有不一致的地方。
1.新事件很是不穩定,可能還有普及就開始被廢棄,在早期的sizzle選擇器引擎中,有這麼幾句。
document.addEventListener("DOMAttrModified", invalidate, false); document.addEventListener("DOMNodeInserted", invalidate, false); document.addEventListener("DOMNodeRemoved", invalidate, false);
如今這三個事件被廢棄了(準確的說,全部變更事件都完蛋了),FF14和chrome18開始使用MutationObserver代替它。
2.Firefox不支持focusin,focus事件,也不支持DOMFocusIn,DOMFocusOut,如今也不肯意用mouseWheel代替DOMMouseScroll。chrome不支持mouseenter與mouseleave.
所以,不要覺得標準瀏覽器就確定實現了w3c標準事件,全部特徵偵測必不可少。
3.第三個,第四個,第五個標準參數。
第三個參數,useCapture只有很是新的瀏覽器纔是可選項。好比FF6或以前是可選的,爲了安全起見,確保第三個參數爲布爾。
4.事件成員的不穩定。
w3c是從瀏覽器商抄過來的,人家用了這麼久,不免與標準不一致。
ff下event.timeStamp返回0的問題,這個bug,2004年就有人提交了,直到2011年才被修復。
Safari下event.target可能返回文本節點
event.defaultPrevented,event.isTrusted與stopImmediatePropagation方法,以前標準瀏覽器都統一用getpreventDefault方法作這個事情,在jQuery源碼中,發現它是用isDefaultPrevented來處理。
isTrusted屬性用於表示當前事件是不是由用戶行爲觸發,好比是用一個真實的鼠標點擊觸發click事件,仍是由一個腳本生成的(使用事件構造方法,好比event.initEvent)。isTrusted請多關注
5.標準瀏覽器沒有辦法模擬像IE6-8的proprtychange事件。
雖然標準的瀏覽器有input, DOMAttrModified,MutationObserver,但比起propertychange弱爆了。propertychange能夠監聽多種屬性變化,而不僅僅是value值。另外它不區分attribute和property。所以,不管是經過el.xxx = yyy 仍是el.setAttribute(xxx,yyy)都接觸此事件。
http://www.cnblogs.com/rubylouvre/archive/2012/05/26/2519263.html (判斷瀏覽器是否支持DOMAttrModified)
四,Dean Edward的addEvent.js源碼分析
這是一個prototype時代早期出現的一個事件系統。jQuery事件系統源頭。亮點以下:
1.有意識的屏蔽IE與w3c在阻止默認行爲與事件傳播接口的差別。
2.處理ie執行回調時的順序問題
3.處理ie的this指向問題
4.沒有平臺檢測代碼,由於是使用最通用最原始的onXXX構建
5.徹底跨瀏覽器(IE4與NS4)。
此處省略源碼分析
http://dean.edwards.name/weblog/2005/10/add-event/
// written by Dean Edwards, 2005 // with input from Tino Zijdel, Matthias Miller, Diego Perini // http://dean.edwards.name/weblog/2005/10/add-event/ function addEvent(element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else { // assign each event handler a unique ID if (!handler.$$guid) handler.$$guid = addEvent.guid++; // create a hash table of event types for the element if (!element.events) element.events = {}; // create a hash table of event handlers for each element/event pair var handlers = element.events[type]; if (!handlers) { handlers = element.events[type] = {}; // store the existing event handler (if there is one) if (element["on" + type]) { handlers[0] = element["on" + type]; } } // store the event handler in the hash table handlers[handler.$$guid] = handler; // assign a global event handler to do all the work element["on" + type] = handleEvent; } }; // a counter used to create unique IDs addEvent.guid = 1; function removeEvent(element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false); } else { // delete the event handler from the hash table if (element.events && element.events[type]) { delete element.events[type][handler.$$guid]; } } }; function handleEvent(event) { var returnValue = true; // grab the event object (IE uses a global event object) event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event); // get a reference to the hash table of event handlers var handlers = this.events[event.type]; // execute each event handler for (var i in handlers) { this.$$handleEvent = handlers[i]; if (this.$$handleEvent(event) === false) { returnValue = false; } } return returnValue; }; function fixEvent(event) { // add W3C standard event methods event.preventDefault = fixEvent.preventDefault; event.stopPropagation = fixEvent.stopPropagation; return event; }; fixEvent.preventDefault = function() { this.returnValue = false; }; fixEvent.stopPropagation = function() { this.cancelBubble = true; };
不過在Dean Edward對應的博文中就能夠看到許多指正與有用的patch。好比說,既然全部的修正都是衝着IE去的,那麼標準瀏覽器用addEventListener就行。有的還提到,在iframe中點擊事件時,事件對象不對的問題,提交如下有用的補丁。
event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
其中,第54條回覆,直接致使了jQuery數據緩存系統的產生,爲了不交錯引用產出的內存泄露,建議元素分配一個uuid,全部的回調都放在一個對象中存儲。
但隨着事件的推移,使用者發現onXXX在IE存在不可消除和彌補的內存泄露,所以,翻看jQuery早期的版本,1.01是照抄Dean Edward的,1.1.31版本,開始吸取54條uuid的建議,並使用attach/removeEventListener綁定事件——每一個元素只綁定一次。而後全部回調都在相似handleEvent的函數中調用。
五,jQuery1.8.2的事件模塊概述
jQuery的事件模塊發端於Dean Edward的addEvent,而後它不斷吸取社區的插件與補丁,發展成爲很是棒的事件系統。其中不得不提的是事件代理與事件派發機制。
早在07年,Brandon Aaron爲jQuery寫了一個劃時代的插件,叫livequery,它能夠監聽後來插入的元素的事件。好比說,一個表格,咱們爲tr元素綁定了mouseover/mouseout事件時,只有十行代碼,而後咱們又動態加載了20行,這20個tr元素一樣能執行mouseover/mouseout回調。魔術在於,它並無把事件偵探器綁定在tr元素上,而是綁定在最頂層的document上,而後經過事件冒泡,取得事件源,斷定它是否匹配給用戶給定的css表達式,才執行用戶回調、具體參考它的github:
https://github.com/brandonaaron/livequery
若是一個表格有100個 tr元素,每一個都要綁定mouseover/mouseout事件,改爲事件代理的方式,能夠節省99次綁定,這優化很好,更況且它能監聽未來添加的元素,所以被立馬吸取到jquery1.3中去,成爲它的live方法,再把一些明顯的bug修復了。jquery1.32成爲最受歡迎的版本。與後來的jquery1.42都是歷程碑式的。
不過話說回來,live方法須要對某些不冒泡的事件作些處理,好比一些表單事件,有的只能冒泡的form,有的只能冒泡到document,有的根本就不冒泡。
ie6 | ie8 | ff3.6 | opera10 | chome4 | sfari4 | |
submit | form | form | document | document | document | document |
reset | form | form | document | document | form | form |
change | 不冒泡 | 不冒泡 | document | document | 不冒泡 | 不冒泡 |
click | document | document | document | document | document | document |
select | 不冒泡 | 不冒泡 | document | document | 不冒泡 | 不冒泡 |
對於focus, blur, change, submit, reset, select等不會冒泡的事件,在標準瀏覽器中,咱們咱們能夠設置addEventListener最後的一個參數爲true輕鬆搞定,在IE就有點麻煩了,要用focusin代替focus,focusout代替blur,selectsatrt代替select,change和submit,reset就更復雜了。必須使用其餘的事件來模擬,還要判斷事件源的類型,slelectedIndex, keyCode等相關屬性。
這個課題到最後由一個叫reglib的庫搞定,reglib的做者還寫過一篇很著名的文章,《goodbuy mouseover,hello mouseenter》,來推廣微軟系統的兩個事件,mouseenter與mouseleave。jQuery全面接納了他們。
live方法帶來的全新體驗是空前的,但畢竟要冒泡到最頂層,對IE來講有點坎坷,還會失靈。最好能指定父節點,一個綁定時已經存在的父節點。這樣就不用費力了。當時有三篇博文給出了相近的方案,他們給出的接口一篇比一篇接近jhhon Resig接納的方案。
http://danwebb.net/2008/2/8/event-delegation-made-easy-in-jquery
http://raxanpdi.com/blog-jquery-event-delegate.html (jQuery Event Delegation)
http://blog.threedubmedia.com/2008/10/jquerydelegate.html(已經打不開)
那時,它已是這個樣子
$('div').delegate('click','span',function (event) { $(this).toggleClass('selected'); return false; })
並提出解除代理的API:undelegate
在jquery1.42在2010年2月19推出時,也這兩個接口,前面的兩個參數只是掉換一下。
$('div').delegate('span','click',function( event ){ $( this ).toggleClass('selected'); return false; })
正所衆人拾柴火焰高,jquery強大無不道理,在jquery1.8中,它又吸取dperini/nwevents的點子,改進其代理事件,大大 提升了性能。
先看一下其主要接口:
Jquery事件接口:bind live delegate on one trigger hover unbind die undelegate off toggle triggerHander Event .
其中bind,unbind,one,trigger,toggle,hover,ready一開始就有。
triggerHandler是jQuery1.23增長的,內部依賴於trigger,只對當前匹配元素的第一個有效,不冒泡不觸發默認行爲。
live與die是jQuery1.3增長的,用於事件代理,統一由document代理
delegate與undelegate是jquery1.4增長的,容許指定代理元素的事件代理,它內部是利用live,die實現的。
on與off是jQuery1.7增長的,目的是統一事件接口,bind,one,live,delegate,直接由on衍生,unbind,die,dundelegate直接由off衍生。
hover用於模擬css的hover效果,內部依賴於mouseenter,mouseleave
ready能夠看作是load事件的加強版,獲取最先的DOM可用後,當即執行各類dom操做。
toggle是click的加強版,每次點擊都執行不一樣的回調,並切換到下一個。
triggle與triggleHander是jQuery的fireEvent實現
此外,jQuery還有25個事件類型命名的快捷方法,當參數個數爲2時表現爲綁定事件。個數爲0時表現爲派發事件。
jQuery快捷事件:contextmenu, error, keyup, keypress, keydown, submit, select, change, mouseleave, mouseenter, mouseout, mouseover, blur, focus, focusin, focusout, load, resize, scroll, unload, click, dbclik,mousedown, mouseup, mousemove
不過,最重要最基礎的設施無疑是jQuery.event之下
add方法用於綁定事件。
remove方法用於卸載事件。
dispath方法用於統一用戶回調,至關於Dean Edward的handleEvent方法,所以,jQuery1.7前它的名字一直叫handle。
trigger用於事件派發。
fix用於修正事件對象。
自jQuery1.4起,jQuery大大強化自定義事件功能。special對象原本是用於修正個別事件的,如今它容許這些事件經過setup, teardown, add, remove, default這些接口實現DOM事件的各類行爲。
參考:
http://benalman.com/news/2010/03/jquery-special-events/
六,jQuery.event.add源碼解讀
add方法的主要目的,將用戶全部的傳遞參數,合併成一個handleObj對象放到元素對應的緩存體中events對象的某個隊列中,而後綁定一個回調。這個回調會處理用戶的全部回調。所以,對於每一個元素的每一種事件,它只綁定一次。
var add = function(elem, types, handler, data, selector) { var elemData, eventHandle, events, t, tns, namespaces, handleObj; //若是elem不能添加自定義屬性,因爲ie下訪問文本節點會報錯,所以事件源不能是文本節點。 //註釋節點原本就不該該綁定事件,註釋節點之因此混進來,是由於jQuery的html方法所致 //若是沒有指定事件的類型或回調也當即返回,再也不向下操做 if (elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data(elem))) { return; } //取得用戶回調與css表達式,handleObjIn這種結構咱們稱爲事件描述 //記下用戶綁定此回調的各類信息,方便用於「事件拷貝」 if (handler.handler) { handlerObjIn = handler; handler = handlerObjIn.handler; selector = handlerObjIn.selector; } //確保回調有uuid,用於查找和移除 if (!handler.guid) { handler.guid = jQuery.guid++; } //爲元素在數據緩存系統中開闢一個叫"event"的空間來保存其全部回調與事件處理器 events = elemData.events; if (!events) { elemData.events = events = {}; } eventsHandle = elemData.handle; //事件處理器 if(eventHandle) { elemData.handle = eventHandle = function(e) { //用戶在事件冒充時,被第二次fire或者頁面的unload後觸發 return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply(eventsHandle.elem, arguments) : undefined; }; //原註釋是說,防止IE下非原生的事件內存泄露,(直接影響是明確了this的指向) eventHandle.elem = elem; } //經過空格隔開同時綁定多個事件,好比jQuery(...).bind("mouseover mouseout", fn); types = jQuery.trim(hoverHack(types)).split(" "); for (t = 0 ; t < types.length; t++) { tns = rtypenamespace.exec(types[t]) || [] ; //取得命名空間 type = tnsp[1];//取得真正的事件 namespaces = (tns[2] || "").split(".").sort();//修正命名空間 //並非全部事件都能直接使用,好比FF下沒有mousewheel,須要用DOMMouseScroll冒充 special = jQuery.event.special[ type ] || {}; //有時候,咱們只須要在事件代理時進行冒充,好比FF下的focus, blur type = (selector ? special.delegateType : special.bindType) || type; special = jQuery.event.special[type] || {}; //構建一個事件描述對象 handleObj = jQuery.extend({ type : type, origType : tns[1], data : data, handler : handler, guid : handler.guid, selector : selector, needsContext : selector && jQuery.expr.match.needsContext.test(selector), namespace : namespaces.join(".") },handlerObjIn); //在events對象上分門別類的存儲事件描述,每種事件對於一個數組 //每種事只綁定一次監聽器(既addEventListener, attachEvent) handlers = events [type]; if (!handlers) { handlers = events[ type ] = []; handlers.delegateCount = 0; //記錄要處理的回調個數 //若是存在special.setup而且special.setup返回0個才直接使用多投事件API if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) { if (elem.addEventListener) { elem.addEventListener(type, eventHandle, false); } else if (elem.attachEvent) { elem.attachEvent("on" + type, eventHandle); } } } if (special.add) { //處理自定義事件 special.add.call(elem, handleObj); if (!handleObj.handler.guid) { handleObj.handler.guid = handler.guid; } } //add to the element's hanlder list, deleate in front if (selector) { //若是是事件代理,那麼把此事件代理描述放在數據的前面 handlers.splice(handlers.delegateCount++, 0, handleObj); } else { handlers.push(handleObj); } //用於jQuery.event.trigger,若是此事件從未綁定過,也不必進入trigger的真正處理邏輯 jQuery.event.golbal[ type ] = true; } //防止ie內存泄露 elem = null }
從上面的註釋中咱們能夠得出,jQuery的回調再也不直接與元素掛鉤,而是經過uuid訪問數據緩存系統,抵達對於的events對象,再根據事件類型獲得一組事件描述。而且事件描述裏邊沒有事件源的記錄,所以,很是方便導出挪動,爲事件克隆大開方便之門。固然,其中數據緩存系統是關鍵,接着探索,就會發現,其它事件代理部分對數據緩存依賴的更嚴重。
下面是元素數緩存與事件描述之間的結構:
在firebug下查看結構:以下
七,jQuery.event.remove的源碼解讀
remove方法的主要目的是,根據用戶傳參,找到事件隊列,從裏邊把匹配的handleObj對象移除,在參數不足的狀況,可能移除N個或全部。當隊列的長度爲零就移除事件,當events爲空對象,則清除掉UUID。
remove = function(elem, types, handler, selector) { var t, tns, type, origType,namespaces, origCount, j, events, special, eventType, handleObj, elemData = jQuery.hasData( elem ) && jQuery._data( elem ); //若是不支持添加自定義屬性或沒有緩存與事件有關的東西,當即返回。 if ( !elemData || !(events = elemData.events)) { return; } //hover轉換爲"mouseenter mouseleave",而且按照空格進行切割,方便移除多種事件類型 types = jQuery.trim( hoverHack( types || "")).split(" "); for (t = 0; t < types.length; t++) { tns = rtypenamespace.exec(types[t]) || []; type = origType = tns[1]; //取得事件類型 namespaces = tns[2];//取得命名空間 if (!type) { //若是沒有指定事件類型,則移除全部事件類型或移除全部與此命名空間相關的事件類型 for (type in events) { jQuery.event.remove( elem, type + types[t], handler, selector, true); } continue; } //利用事件冒充,取得真正的用於綁定事件類型 special = jQuery.event.special[ type ] || {}; type = (selector ? special.delegateType : special.bindType) || type; eventType = events[type] || []; //取得裝載事件描繪對象的數組 origCount = eventType.length; //取得用於過濾命名空間的正則,沒有爲null namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)" : null ; //移除符合條件的事件描述 for ( j = 0; j < eventType.length; j++) { handleObj = eventType[ j]; if (( origType === handleObj.guid ) & //比較事件類型是否一致 (!handler || handler.guid === handleObj.guid) && //若是傳遞了回調,斷定UUid是否相同 //若是types含義命名空間,用正則看是否匹配 //若是事件代理必有css表達式,比較與事件描述對象中是否相等 (!selector || selector === handleObj.selector || selector === "**" && handleObj.selector)) { eventType.splice(j-- , 1); //是就移除 if ( handleObj.selector ) { //同時delegateCount減去1 eventType.delegateCount--; } if (special.remove) { //處理個別事件移除 special.remove.call( elem, handleObj); } } } //若是已經移除全部此類問題,則卸載框架綁定去的elemData.handle //origCount !== eventType.length 是爲了防止死循環 if ( eventType.length === 0 && origCount !== eventType.length) { if (!special.teardown || special.teardown.call(elem, namespaces, elemData.handle ) === false) { jQuery.removeEvent (elem, type, elemData.handle); } delete events[ type ]; } } //若是events爲空,則從elemData中刪除events與handler if ( jQuery.isEmptyObject( events )) { delete elemData.handle; jQuery.removeData (elem, "events", true) } }
事件卸載部分是jQuery事件系統中最簡單的部分,主要邏輯都花在移除事件描述對象和匹配條件上。
八,jQuery.event.dispatch的源碼解讀
這是jQuery事件系統的核心。它就是利用這個dispatch方法,從緩存體中的events對象取得對於的隊列,而後修復事件對象,逐個傳入用戶的回調中執行,根據返回值決定是否斷開循環(stopImmediatePropagation),阻止默認行爲和事件傳播。
dispatch = function(event) { //建立一個僞事件對象(jQuery.Event實列),從真正的實際對象上抽得響應的屬性附於其上 //若是是iE,也能夠將其轉換成對於的W3c屬性,抹去兩大平臺的差別。 event = jQuery.event.fix(event || window.event); var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related, //取得全部事件描述對象 handlers = ((jQuery._data(this, "events") || {}) [ event.type ] || []), delegateCount = handlers.delegateCount, args = core_slice.call(arguments), run_all = !event.exclusive && !event.namespace, special = jQuery.event.special[ event.type ] || {}, handlerQueue = []; //重置第一個參數爲jQuery.Event實例 args[0] = event; event.delegateTarget = this; //添加一我的爲的屬性,用於事件代理 //執行preDispatch回調,它與後面的postDispatch構成一種相似AOP的機制 if (special.preDispatch && special.preDispatch.call(this, event) === false) { return; } //若是是事件代理,而且不是來自非左鍵的點擊事件 if (delegateCount && !(event.button && event.type === "click")) { //從事件源開始,遍歷其全部祖先一直到綁定事件的元素 for ( cur = event.target; cur != this; cur = cur.parentNode || this) { //不要觸發被disabled元素的點擊事件 if (cur.disabled !== tru event.type !== "click") { selMatch = {}; //爲了節能起見,每種CSS表達式只斷定一次,經過下面的 //jQuery( sel, this).index( cur ) >= 0 或 jQuery.find(sel, this, null, [ cur ].length) matches = []; //用於收集符合條件的事件描述對象 //使用事件代理的事件描述對象老是排在最前面 for (i = 0; i < delegateCount; i++ ) { handleObj = handler[ 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}); } } } } //取得其餘直接綁定的事件描述對象 if (handlers.length > delegateCount) { handlerQueue.push({elem : this, matches : handlers.slice(delegateCount) }) } //注意:這個循環是從下到上執行的 for (i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++) { matched = handlerQueue[ i ]; event.currentTarget = matched.elem; //執行此元素的全部與event.type同類型的回調,除非用戶調用了stopImmediatePropagation方法,它會致使isImmediatePropagetionStopped返回true,從而中斷循環 for (j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++) { handleObj = matched.matches[j]; //最後的過濾條件爲事件命名空間,好比著名的bootstrap的命名空間爲data-api if (run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test(handleObj.namespace)) { event.data = handleObj.data; event.handleObj = handleObj; //執行用戶回調,(有時 可能還要外包一層,來自jQuery.event.specal[type].handle) ret = ((jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler).apply(matched.elem, args); //根據結果斷定是否阻止事件傳播與默認行爲 if (ret !== undefined) { event.resulet = ret; if (ret === false) { // event.preventDefault(); event.stopPropagation(); } } } } } //執行postDispatch回調 if (special.postDispatch) { special.postDispatch.call(this, event); } return event.resulet; }
本節的難點在於如何模擬事件的傳播機制,jQuery實際上只模擬冒泡那一階段。
九,jQuery.event.trigger解讀
籠統的來講,triggter就是dispatch的增強版。
dispatch只觸發當前元素與其底下元素(經過事件代理的方式)的回調,trigger則模擬整個冒泡過程,除了它自身,還觸發其祖先節點與window的同類型的回調。不過,從trigger的代碼來看,它比dispatch多作的事情就是觸發事件的默認行爲。
這涉及到太多的斷定,若是再把dispatch的代碼寫在一塊就很差維護了。
可是,我以爲,trigger其實用不着那麼複雜,trigger要作的事情就是在某一元素觸發一個回調(dispatch),生產一個事件對象,而後讓其順序冒泡,觸發其它的回調(dispatch)就好了。
瀏覽器提供了原生的派發機制,IE下的fireEvent,標準瀏覽器爲dispatchEvent.
若是在控制檯執行如下函數,咱們會獲得更多的事件構造器
Object.getOwnPropertyNames(window).filter(function (p) { return typeof window[p] == "function" && (window[p].prototype instanceof Event) })
["MIDIMessageEvent", "MIDIConnectionEvent", "MediaKeyMessageEvent", "MediaEncryptedEvent", "webkitSpeechRecognitionEvent", "webkitSpeechRecognitionError", "StorageEvent", "SpeechSynthesisEvent", "MediaStreamEvent", "IDBVersionChangeEvent", "GamepadEvent", "DeviceOrientationEvent", "DeviceMotionEvent", "CloseEvent", "OfflineAudioCompletionEvent", "AudioProcessingEvent", "MediaKeyEvent", "XMLHttpRequestProgressEvent", "WheelEvent", "WebGLContextEvent", "UIEvent", "TransitionEvent", "TrackEvent", "TouchEvent", "TextEvent", "SecurityPolicyViolationEvent", "SVGZoomEvent", "ProgressEvent", "PopStateEvent", "PageTransitionEvent", "MutationEvent", "MouseEvent", "MessageEvent", "MediaQueryListEvent", "KeyboardEvent", "HashChangeEvent", "FocusEvent", "ErrorEvent", "CustomEvent", "CompositionEvent", "ClipboardEvent", "BeforeUnloadEvent", "AutocompleteErrorEvent", "ApplicationCacheErrorEvent", "AnimationEvent", "WebKitAnimationEvent", "WebKitTransitionEvent"]
(webkit包含)
但經常使用的交互都集中在HTML4.0已經定義好的事件上,咱們無需理會message storage popstate事件,更別提事件變更。咱們認爲支持如下事件就行了。
HTMLEvents | Load, unload, abort, error, select, change, submit, reset, focus, blur, resize, scroll |
KeyboardEvent | keypress keydown keyup |
MouseEvents | contextmenu, click, dblclick, mouseout, mouseover, mouseenter, mouseleave, mousemove,mousedown, mouseup,mousewheel |
根據上面的表格,咱們作一個叫eventMap的hash出來,那麼trigger方法最大限制級能夠壓縮成以下樣子:
trigger: function (type, target) { var doc = target.ownerDocument || target.document || target || document ; event = doc.createEvent(eventMap[type] || "CustomEvent"); if (/^(focus|blur|select|submit|reset)$/.test(type)) { target[type] && target[type] (); //觸發默認行爲 } Event.initEvent( type, true, true); target.dispatchEvent(event); }
可是這樣trigger出來的對象是隻讀,不能覆蓋原生屬性或方法,所以,你能夠覺得它自定義一個more屬性,裏邊裝載着你要改的東西,而後在dispatch將它包裝成一個jQuery僞事件對象後,在把循環加在僞事件對象就行 了。
十,jQuery對事件對象的修復(不更新)
十一,滾輪事件的修復(不更新)
十二,mouseenter與mouseleave事件的修復(不更新)
十三,focus與focusout事件的修復(不更新)
十四,舊版本下IE的submit的事件代理實現(不更新)
在舊版本的IE下submit不會冒泡到頂層,它只執行form元素的submit回調,並當即執行提交跳轉,所以只能用事件冒充的方式來實現。
咱們看看什麼狀況下瀏覽器觸發submit事件吧。submit事件與鼠標事件,鍵盤事件是不同的。它是一種複合事件,既可使用鼠標實現,也能夠經過鍵盤事件實現,重要的結果,能實現表單提交便可。
當焦點聚焦於input[type=text]、input[type=password]、input[type=checkbox]、input[type=radio]、input[type=button]、input[type=image]、input[type=submit]、按回車鍵就會觸發提交
當鼠標在input[type=image],input[type=submit]上方,經過點擊事件就會觸發提交
瀏覽器差別,IE下能夠在input=file回車後觸發
咱們也可使用form.submit()這樣的編程手段觸發
IE下submit, reset的事件代理,delegate用於檢測它有沒有用事件代理,不用管它。重點在於它在代理元素上一下綁定了兩個事件,click,keypress,若是是鍵盤事件,就判斷他的keycode是否爲13108(回車鍵)
el.submit()方法有個特異之處,它是不會執行submit回調的,像其餘click,blur,focus,select這樣的DOM方法都會同時執行回調與默認行爲。
十五,oninput事件的兼容性性處理(不更新)