上一篇說到,咱們在最外層API的on,off,tiggler,triggerHandler調用的是event方法的add,remove和tirgger方法,本篇就來介紹event輔助類node
\api
先放個圖,這其實就是整個事件流程的過程:數組
1. add方法:瀏覽器
add: function(elem, types, handler, data, selector) { var handleObjIn, eventHandle, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, elemData = data_priv.get(elem); // Don't attach events to noData or text/comment nodes (but allow plain objects)
//取出dom對象的數據緩存對象,若是爲空或是text,comment節點,直接退出,不會綁定事件 if (!elemData) { return; } // Caller can pass in an object of custom data in lieu of the handler if (handler.handler) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } // Make sure that the handler has a unique ID, used to find/remove it later
//guid是否存在,不存在建立一個,用於標示和刪除事件 if (!handler.guid) { handler.guid = jQuery.guid++; } // Init the element's event structure and main handler, if this is the first
//檢測數據緩存中是否存在事件隊列,沒有建立一個,用於保存綁定的事件 if (!(events = elemData.events)) { events = elemData.events = {}; } //爲數據緩存添加一個事件處理器,後面會用到 if (!(eventHandle = elemData.handle)) { eventHandle = elemData.handle = function(e) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply(eventHandle.elem, arguments) : undefined; }; // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
//定義事件處理器對應的元素,用於防止IE非原生事件中的內存泄露 eventHandle.elem = elem; } // Handle multiple events separated by a space // jQuery(...).bind("mouseover mouseout", fn); // core_rnotwhite:/\S+/g
//多個事件經過空格拆分 types = (types || "").match(core_rnotwhite) || [""]; // 例如:'.a .b .c'.match(/\S+/g) → [".a", ".b", ".c"] t = types.length;
//對每一個事件進行處理 while (t--) { //命名空間處理 如"mouseover.a.b" → tmp = ["mouseover.a.b", "mouseover", "a.b"] tmp = rtypenamespace.exec(types[t]) || []; type = origType = tmp[1]; namespaces = (tmp[2] || "").split(".").sort();//namespace = ['a','b'] // There *must* be a type, no attaching namespace-only handlers
//若是事件不存在,不處理 if (!type) { continue; } // If event changes its type, use the special event handlers for the changed type
//兼容性處理,後面再說 special = jQuery.event.special[type] || {}; // If selector defined, determine special event api type, otherwise given type type = (selector ? special.delegateType : special.bindType) || type; // Update special based on newly reset type special = jQuery.event.special[type] || {}; // handleObj is passed to all event handlers
//建立事件處理對象,這裏保存了事件的各類屬性,包括事件名稱,數據,guid,委託標籤(selector),處理函數等等 handleObj = jQuery.extend({ type: type, origType: origType, data: data, handler: handler, guid: handler.guid, selector: selector, needsContext: selector && jQuery.expr.match.needsContext.test(selector), namespace: namespaces.join(".") }, handleObjIn); // Init the event handler queue if we're the first
// 針對當前事件類型,初始化事件處理列隊,若是是第一次使用,則執行初始化
if (!(handlers = events[type])) {
handlers = events[type] = []; handlers.delegateCount = 0; // Only use addEventListener if the special events handler returns false
//真正的邏輯在這裏,先不考慮兼容性處理,把上面的事件處理器綁定到DOM對象中去,jQuery2.0.3不兼容低版本瀏覽器,因此addEventListener是通用的 if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) { if (elem.addEventListener) { elem.addEventListener(type, eventHandle, false); } } } //兼容性處理,先不看 if (special.add) { special.add.call(elem, handleObj); if (!handleObj.handler.guid) { handleObj.handler.guid = handler.guid; } } // Add to the element's handler list, delegates in front
//若是有委託參數,將委託函數插入隊列的前排頭等艙位置 if (selector) { handlers.splice(handlers.delegateCount++, 0, handleObj); } else {
//不然追加到隊列後面 handlers.push(handleObj); } // Keep track of which events have ever been used, for event optimization
//標示當前事件曾經處理過,用於事件優化 jQuery.event.global[type] = true; } // Nullify elem to prevent memory leaks in IE
// 設置爲null避免IE中循環引用致使的內存泄露
elem = null;
},
總體思路是爲當前的dom對象添加兩個屬性,一個是事件處理隊列event_list,一個是事件處理器eventHandler,對用戶輸入的全部事件進行循環處理,建立事件對象保存到event_list中去,若是事件帶有委託對象,則放到前排,不然追加到event_list,再把eventHandler綁定到DOM節點中這個事件中去,最終相似下面的結構:緩存
<div id="#main_div"><span></span></div>
<script> function handler1(){}; function handler2(){} $("#main_div").on("click",'span',handler1) .on("click",handler2); </script>
建立爲:
elemData = jQuery._data( elem ); elemData = { events: { click: {//Array[2] 0: { data: undefined/{...}, guid: 2, //處理函數的id handler: function handler1(){…}, namespace: "", needsContext: false, origType: "click", selector: "span", type: "click" } 1: { data: undefined, guid: 3, handler: function handler2(){…}, namespace: "", needsContext: false, origType: "click", selector: undefined, type: "click" } delegateCount: 1,//委託事件數量,有selector的纔是委託事件 length: 2 } } handle: function ( e ) {…}{//click事件會觸發這個處理函數 elem: document//屬於handle對象的特徵 } }
回到上面的eventHandle = elemData.handle,當事件在瀏覽器觸發時,會觸發jQuery.event.dispatch.apply(eventHandle.elem, arguments) ,也就是執行dispatch方法:數據結構
2. dispatch方法:app
dispatch: function(event) { // Make a writable jQuery.Event from the native event object // 經過原生的事件對象建立一個可寫的jQuery.Event對象 event = jQuery.event.fix(event); var i, j, ret, matched, handleObj, handlerQueue = [], args = core_slice.call(arguments), handlers = (data_priv.get(this, "events") || {})[event.type] || [], special = jQuery.event.special[event.type] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event args[0] = event; // 事件的觸發元素 event.delegateTarget = this; // Call the preDispatch hook for the mapped type, and let it bail if desired if (special.preDispatch && special.preDispatch.call(this, event) === false) { return; } // Determine handlers
//handlers就是前面的elementData.event,事件列表
//調用event.handlers方法過濾當前 handlerQueue = jQuery.event.handlers.call(this, event, handlers); // Run delegates first; they may want to stop propagation beneath us i = 0; // 遍歷事件處理器隊列{elem, handlerObjs}(取出來則對應一個包了),且事件沒有阻止冒泡 while ((matched = handlerQueue[i++]) && !event.isPropagationStopped()) { event.currentTarget = matched.elem; j = 0; // 若是事件處理對象{handleObjs}存在(一個元素可能有不少handleObjs),且事件不須要馬上阻止冒泡 while ((handleObj = matched.handlers[j++]) && !event.isImmediatePropagationStopped()) { // Triggered event must either 1) have no namespace, or // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). // Triggered event must either 1) have no namespace, or // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). // 觸發的事件必須知足其一: // 1) 沒有命名空間 // 2) 有命名空間,且被綁定的事件是命名空間的一個子集 if (!event.namespace_re || event.namespace_re.test(handleObj.namespace)) { event.handleObj = handleObj; event.data = handleObj.data; // 嘗試經過特殊事件獲取處理函數,不然使用handleObj中保存的handler(因此handleObj中還保存有handler(事件處理函數)) // handleObj.origType 定義的事件類型 // handleObj.handler 事件處理函數 // 終於到這裏了,開始執行事件處理函數 ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler) .apply(matched.elem, args); // 檢測是否有返回值存在 if (ret !== undefined) { // 若是處理函數返回值是false,則阻止冒泡,阻止默認動做 if ((event.result = ret) === false) { event.preventDefault(); event.stopPropagation(); } } } } } // Call the postDispatch hook for the mapped type if (special.postDispatch) { special.postDispatch.call(this, event); } return event.result; },
整個過程就是:dom
1. 當瀏覽器事件觸發時,會觸發dispatch方法並傳入瀏覽器event對象。函數
2. jQuery顯然不滿意原生event對象,經過fix方法重寫了event對象,除了繼承了event對象,還添加了一些本身的屬性,變成了event對象加強版。post
3. 取出當前dom對象數據緩存中的Event列表,也就是事件列表。
4. 調用event.handler方法,把事件列表(handlers,也就是elemData.event)傳遞進去,對事件列表進行遍歷查詢,把須要執行的事件對象提取出來放到handlerQueue中去。
5. 對handlerQueue進行遍歷,當存在handlerQueue對象而且事件沒有阻止冒泡,則最終會調用事件對象函數,也就是調用以前綁定的事件對象函數。
上面是主要的4個步驟,實際上每一個步驟都有不少細節要說:
1. 這個很簡單,原生事件到jQuery事件的入口。
2. fix方法,返回了增強版的event對象,主要做用是瀏覽器兼容性處理,加入了jQuery的本身方法,以及能夠建立自定義對象等等。
(1)首先對象的類型適配,keyEvent和mouseEvent的事件對象屬性是不一樣的,這裏經過fixHooks來進行適配,jQuery.event.props包含了事件對象的公用屬性
而後mouseHooks和keyHooks分別包含了鼠標和鍵盤事件的不一樣對象,經過下面的邏輯進行合併,將公有屬性和私有屬性concat到一塊兒,實現了動態適配。
if (!fixHook) { this.fixHooks[type] = fixHook = rmouseEvent.test(type) ? this.mouseHooks : rkeyEvent.test(type) ? this.keyHooks : {}; } copy = fixHook.props ? this.props.concat(fixHook.props) : this.props;
而後參加event = new jQuery.Event(originalEvent);建立增強版event,Event對象下面會說,先執行下面邏輯
i = copy.length; while (i--) {//將原生對象的屬性值拷貝過來 prop = copy[i]; event[prop] = originalEvent[prop]; }
//兼容性處理,Cordova不支持target屬性,這裏添加上 if (!event.target) { event.target = document; }
// text節點不能是target,查找其父節點 if (event.target.nodeType === 3) { event.target = event.target.parentNode; } //鉤子方法,將keyEvent和mouseEvent的兼容性處理,見相應的filter方法,好比添加which屬性,pageX和pageY屬性等等。 return fixHook.filter ? fixHook.filter(event, originalEvent) : event;
最後返回一個全新的Event對象,該對象包含了舊對象的全部方法,還新增了兼容性處理的屬性(pageX等),兼容了target屬性等等。
(2)建立Event對象:Event構造函數的主要做用就是重寫事件默認對象方法,兼容時間戳屬性this.timeStamp = src && src.timeStamp || jQuery.now();由於部分瀏覽器這塊有差別,這裏統一處理。下面是對象方法:
重寫了preventDefault,stopPropagation,stopImmediatePropagation方法,只是在原有的基礎上新增了是否調用過的判斷,是否調用過就是增長的isDefaultPrevented,isPropagationStopped和isImmediatePropagationStopped幾個方法。、
3. handlers = (data_priv.get(this, "events") || {})[event.type] || [];取出事件列表
4. handlerQueue = jQuery.event.handlers.call(this, event, handlers);調用jQuery.event.handlers方法,這個方法主要作兩個邏輯處理
(1)從事件源對象event.target開始,一層一層往父節點遍歷,每遍歷到一個父節點,再遍歷event事件對象列表的前排帶有委託標識(selector)的元素,看看當前節點是否能查詢到委託元素(selector),若是能查到,說明當前節點是被委託處理事件的節點,也就是說源事件發生之後,當前節點是被委託須要響應事件的,就將該事件函數push到handlerQueue數組中,直到全部遍歷完成。
此時handlerQueue數組保存的是當前節點須要處理的須要委託處理的方法。
(2)再把當前節點綁定的事件函數(也就是非委託直接綁定的事件函數) 添加到handlerQueue中去,此時handlerQueue包含了兩種事件函數:
1) 其餘節點委託到本節點的全部函數列表
2) 直接綁定到本節點的函數列表
到此就返回了當前事件須要響應的全部函數列表。
5. 事件函數調用:對於全部的事件函數列表,若是還有存在元素而且沒有被阻止冒泡,遍歷處理:
while ((matched = handlerQueue[i++]) && !event.isPropagationStopped()) {
}
最終的調用方法是:ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem, args);
而後作判斷:
// 檢測是否有返回值存在 if (ret !== undefined) { // 若是處理函數返回值是false,則阻止冒泡,阻止默認動做 if ((event.result = ret) === false) { event.preventDefault(); event.stopPropagation(); } }
if (special.postDispatch) {
special.postDispatch.call(this, event);
}
return event.result;
到此,從事件的添加,到觸發,到兼容處理,再到實際響應就完成了。
回顧一下jQuery的事件流程:
<div id='div_main'> <div id="div_sub"></div> </div> <script> $("#div_main").on("click",function(){ console.log("self click"); }); $("#div_main").on("click","div",function(){ console.log("child click"); }); </script>
1. on方法將參數從新捋一遍而後調用add將兩個方法添加到div_main的緩存數據中,造成如下數據結構:
elemData = jQuery._data( "div_main" ); elemData = { events: { click: { 0: { data: undefined/{...}, guid: 1, handler: function(){console.log("child cilck")}, namespace: "", needsContext: false, origType: "click", selector: "div", type: "click" } 1: { data: undefined, guid: 2, handler: function(){console.log("self click")}, namespace: "", needsContext: false, origType: "click", selector: undefined, type: "click" } delegateCount: 1, length: 2 } } handle: function ( e ) {…}{//click事件會觸發這個處理函數 elem: document//屬於handle對象的特徵 } }
注意下有什麼特色:
(1)委託事件放在前排頭等艙
(2)delegateCount標識了有幾個委託事件,若是爲0則沒有委託事件
(3)selector肯定委託對象
而後添加一個handle回調函數
3. click事件觸發,瀏覽器觸發事件,調用handle方法,啓動dispatch方法
4. 加強event對象爲jQuery.Event對象,適配當前事件對象的屬性,添加公共屬性和私有屬性,能夠添加自定義屬性,從新默認函數,觸發jQuery.event.handlers方法。
5. jQuery.event.handlers:從事件源開始往上遍歷,每一層遍歷再遍歷event事件對象數組,也就是上面的elemData.event,作一下判斷
(1) 若是有委託事件(delegateCount>0),判斷當前節點下是否能找到selector指定的元素,若是有說明須要處理委託事件,將事件觸發對象和事件函數push到handlerQueue
(2) 將綁定到本節點的事件函數直接push到handlerQueue
6. 回到dispatch,對handlerQueue篩選出來的事件函數一一處理。
參考最上面的流程圖
到此,不論是直接綁定的事件函數仍是委託事件函數都已完成。
還有什麼沒說?對,還有兼容性處理special,如今咱們把主線走通了,下一篇說一說特殊狀況的兼容性處理。