jQuery的Event模塊很是強大。其功能遠遠比原生事件監聽器強大許多,對同一個元素的監聽只用一個eventListener,內部則是一個強大的觀察者,根據匹配事件類型觸發相應回調。jQuery不只封裝了兼容性差別,還提供了命名空間式註冊註銷事件,靈活的事件委託(事件代理),手動觸發事件trigger以及自定義事件。由於jQuery提供的bind,delegate,live(1.9版本廢除了)的功能都是經過on來適配的,因此這裏只講on,off,trigger。node
1.註冊事件$.fn.on方法api
1 on: function(types, selector, data, fn, /*INTERNAL*/ one) { 2 var type, origFn; 3 4 // 添加多個事件註冊 5 if (typeof types === "object") { 6 // ( types-Object, selector, data ) 7 if (typeof selector !== "string") { 8 // ( types-Object, data ) 9 data = data || selector; 10 selector = undefined; 11 } 12 // 爲每一個事件迭代 13 for (type in types) { 14 this.on(type, selector, data, types[type], one); 15 } 16 return this; 17 } 18 19 // 若是data和fn都爲空,則將selector賦值給fn, 20 if (data == null && fn == null) { 21 // ( types, fn ) 22 fn = selector; 23 data = selector = undefined; 24 } else if (fn == null) { 25 if (typeof selector === "string") { 26 // ( types, selector, fn ) 27 fn = data; 28 data = undefined; 29 } else { 30 // ( types, data, fn ) 31 fn = data; 32 data = selector; 33 selector = undefined; 34 } 35 } 36 if (fn === false) { 37 fn = returnFalse; 38 } else if (!fn) { 39 return this; 40 } 41 42 // 若是隻是一次性事件,則將fn重新包裝 43 if (one === 1) { 44 origFn = fn; 45 fn = function(event) { 46 // 這裏使用空的jq對象來解除事件綁定信息, 47 // 具體定位是經過event.handleObj和目標元素event.delegateTarget 48 jQuery().off(event); 49 // 執行原始的fn函數 50 return origFn.apply(this, arguments); 51 }; 52 // Use same guid so caller can remove using origFn 53 // 備忘信息 54 fn.guid = origFn.guid || (origFn.guid = jQuery.guid++); 55 } 56 // 統一調用jQuery.event.add方法添加事件處理 57 return this.each(function() { 58 jQuery.event.add(this, types, fn, data, selector); 59 }); 60 }
能夠從源碼看出前面都是針對其餘高層api作的參數調整,最後都會調用jQuery.event.add這個方法來註冊事件。數組
jQuery.event.add方法:瀏覽器
1 /** 2 * 事件綁定最後都經過jQuery.event.add來實現。其執行過程大體以下: 3 1. 先調用jQuery._data從$.cache中取出已有的事件緩存(私有數據,Cache的解析詳見數據緩存) 4 2. 若是是第一次在DOM元素上綁定該類型事件句柄,在DOM元素上綁定jQuery.event.handle,做爲統一的事件響應入口 5 3. 將封裝後的事件句柄放入緩存中 6 傳入的事件句柄,會被封裝到對象handleObj的handle屬性上,此外handleObj還會填充guid、type、namespace、data屬性;DOM事件句柄elemData.handle指向jQuery.event.handle,即jQuery在DOM元素上綁定事件時老是綁定一樣的DOM事件句柄jQuery.event.handle。 7 事件句柄在緩存$.cache中的數據結構以下,事件類型和事件句柄都存儲在屬性events中,屬性handle存放的執行這些事件句柄的DOM事件句柄: 8 elemData = { 9 events: { 10 'click' : [ 11 { guid: 5, type: 'click', namespace: '', data: undefined, 12 handle: { guid: 5, prototype: {} } 13 }, 14 { ... } 15 ], 16 'keypress' : [ ... ] 17 }, 18 handle: { // DOM事件句柄 19 elem: elem, 20 prototype: {} 21 } 22 } 23 */ 24 add: function(elem, types, handler, data, selector) { 25 var tmp, events, t, handleObjIn, 26 special, eventHandle, handleObj, 27 handlers, type, namespaces, origType, 28 // 建立或獲取私有的緩存數據 29 elemData = jQuery._data(elem); 30 31 if (!elemData) { 32 return; 33 } 34 35 // 能夠給jq的handler對象傳參數配置 36 if (handler.handler) { 37 handleObjIn = handler; 38 handler = handleObjIn.handler; 39 selector = handleObjIn.selector; 40 } 41 42 // 確保處理程序有惟一ID,以便查找和刪除 43 // handler函數添加guid屬性 44 if (!handler.guid) { 45 handler.guid = jQuery.guid++; 46 } 47 48 // 首次初始化元素的事件結構和主要處理程序 49 // 緩存數據elemData添加events屬性對象 50 if (!(events = elemData.events)) { 51 events = elemData.events = {}; 52 } 53 // elemData添加handle方法 54 if (!(eventHandle = elemData.handle)) { 55 // 當咱們使用jQuery爲元素添加事件處理程序時, 56 // 實際上就是調用了這個經過包裝的函數, 57 // 而這裏面就是經過jQuery.event.dispatch方法來觸發的 58 eventHandle = elemData.handle = function(e) { 59 // 若是jQuery完成初始化且不存在e或者已經jQuery.event.trigger()了 60 // 返回派遣委託後的結果 61 // this指向eventHandle.elem,解決ie中註冊事件this指向的問題 62 // 若是是IE,這裏使用attachEvent監聽,其事件處理程序的第一個參數就有ie的event了。 63 // 平時說的window.event是指在elem['on' + type] = handler;的狀況 64 return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply(eventHandle.elem, arguments) : 65 undefined; 66 }; 67 // 給handle函數添加elem屬性防止IE非原生內存泄露 68 // handle方法添加elem屬性 69 eventHandle.elem = elem; 70 } 71 72 // 處理空格分離的多事件 73 // jQuery(...).bind("mouseover mouseout", fn); 74 types = (types || '').match(core_rnotwhite) || ['']; 75 t = types.length; 76 while (t--) { 77 tmp = rtypenamespace.exec(types[t]) || []; 78 type = origType = tmp[1]; 79 // 對命名空間進行排序 80 // click.a.c.f.d --- a.c.d.f 81 namespaces = (tmp[2] || '').split('.').sort(); 82 83 // 事件特例(就是爲一些事件類型的一些特殊狀況的處理) 84 special = jQuery.event.special[type] || {}; 85 86 // 若是有事件特例,就使用。不然仍是使用原始type 87 type = (selector ? special.delegateType : special.bindType) || type; 88 89 // 更新事件特例的類型 90 special = jQuery.event.special[type] || {}; 91 92 // 給handleObj添加事件處理程序相關信息, 93 // 若是target對象有相同屬性或方法則替換爲handleObj的 94 handleObj = jQuery.extend({ 95 type: type, 96 origType: origType, 97 data: data, 98 handler: handler, 99 guid: handler.guid, 100 selector: selector, 101 needsContext: selector && jQuery.expr.match.needsContext.test(selector), 102 namespace: namespaces.join('.') 103 }, handleObjIn); 104 105 // 首次初始化事件處理程序隊列 106 if (!(handlers = events[type])) { 107 handlers = events[type] = []; 108 handlers.delegateCount = 0; 109 110 // 當事件特例處理程序沒有setup方法或者setup返回false時使用addEventListener/attachEvent 111 if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) { 112 // 給元素綁定事件處理程序,知道這裏才真正添加事件處理程序 113 if (elem.addEventListener) { 114 elem.addEventListener(type, eventHandle, false); 115 } else if (elem.attachEvent) { 116 elem.attachEvent('on' + type, eventHandle); 117 } 118 } 119 } 120 121 // 事件特例的一些處理 122 if (special.add) { 123 special.add.call(elem, handleObj); 124 125 if (!handleObj.handler.guid) { 126 handleObj.handler.guid = handler.guid; 127 } 128 } 129 130 // 添加元素的事件處理列表, 131 // 若是有selector,則用來給委託事件使用的 132 if (selector) { 133 handlers.splice(handlers.delegateCount++, 0, handleObj); 134 } else { 135 handlers.push(handleObj); 136 } 137 138 // 追蹤哪一個事件曾經被運行過 139 jQuery.event.global[type] = true; 140 } 141 142 // 防止IE內存泄露 143 elem = null; 144 },
該方法會先從jQuery的緩存中查找該元素是否有事件緩存了,確保一個元素只須要一個原生的addEventListener/attachEvent。其實jQuery.event.add這個方法就是拼裝元素事件所需數據,而後還存在緩存系統中,添加原生監聽事件處理。在使用jQuery的方法註冊事件的時候,咱們來看一下$.cache中保存的對應的數據結構:緩存
這是註冊事件代碼,註冊了click,命名空間式的click.ns,dblclick以及自定義的事件custom:數據結構
1 $('#list').on('click', 'li', function(e){ 2 console.log(this); 3 console.log(e); 4 }) 5 .on('dblclick', function(e){ 6 console.log('dblclick'); 7 }) 8 .on('click.ns', function(e){ 9 console.log('ns.click'); 10 }) 11 .on('custom', function(e){ 12 console.log('custom'); 13 });
jQuery緩存系統中對應的事件處理器數據結構:app
jQuery.event.add就是組裝上面的數據結構函數
2是當前元素對應的緩存id,該對象下一級有兩個對象events和handle,events保存着事件處理器,handle就是該元素對應的惟一一個原生事件監聽處理程序的回調。post
events裏面保存着咱們註冊事件時的事件類型key對應的事件處理程序回調列表,回調列表裏面的每一項保存着當前事件的信息,handler就是咱們註冊時的回調。咱們還看到每一個回調列表都會保存着一個delegateCount屬性,這是jQuery計算出委託事件數目,若是用了委託註冊,jQuery先會遍歷你的事件類型,若是隻有一個delegateCount就爲1,不然就爲對應的事件類型個數。delegateCount在後面觸發事件時要用到。ui
既然已經註冊好了,咱們要蓄勢待發了,接着是用戶觸發事件(用戶行爲的觸發事件,非手動觸發trigger),用戶觸發事件會觸發原生的事件處理程序,而後進入到咱們那個元素的對應惟一入口,處罰行爲主要由jQuery.event.dispatch來完成:
1 /** 2 * 派遣事件 3 * 建立jQuery的event對象來代理訪問原生的event, 4 * 經過jQuery.event.handlers計算出委託事件處理隊列handlerQueue(冒泡路徑上的元素),沒有委託則保存着當前元素和保存着其事件處理相關信息的對象handleObj。 5 * 遍歷委託事件處理隊列,再遍歷事件處理數組,找到匹配的事件類型,若是有處理程序,就執行它。可使用event.stopImmediatePropagation()來阻止遍歷下一個事件處理數組項。若是當前元素的當前事件處理程序返回值是false或者內部使用了event.stopPropagation()。就不會遍歷下一個冒泡路徑上的元素了(即當前元素的父級上的元素) 6 * jQuery.event.special[event.type].preDispatch和jQuery.event.special[event.type].postDispatch分別是派遣事件開始和結束的鉤子方法。 7 * @param event 原生event對象 8 * @returns {result|*} 9 */ 10 dispatch: function(event) { 11 // 從原生event中建立jq的event 12 event = jQuery.event.fix(event); 13 14 var i, ret, handleObj, matched, j, 15 handlerQueue = [], 16 args = core_slice.call(arguments), 17 // 獲取元素在jQuery.cache中的events對象的type數組 18 handlers = (jQuery._data(this, 'events') || {})[event.type] || [], 19 // 事件特例 20 special = jQuery.event.special[event.type] || {}; 21 22 // 將第一個event參數替換爲jq的event 23 args[0] = event; 24 // 設置委託目標 25 event.delegateTarget = this; 26 27 // 若是存在preDispatch鉤子,則運行該方法後退出 28 if (special.preDispatch && special.preDispatch.call(this, event) === false) { 29 return; 30 } 31 32 // 委託事件隊列 33 handlerQueue = jQuery.event.handlers.call(this, event, handlers); 34 35 // 先運行委託,若是阻止了冒泡就中止循環 36 i = 0; 37 while ((matched = handlerQueue[i++]) && !event.isPropagationStopped()) { 38 event.currentTarget = matched.elem; 39 40 j = 0; 41 42 // 遍歷當前元素的事件處理程序數組 43 while ((handleObj = matched.handlers[j++]) && !event.isImmediatePropagationStopped()) { 44 // 被觸發的時間不能有命名空間或者有命名空間,且被綁定的事件是命名空間的一個子集 45 if (!event.namespace_re || event.namespace_re.test(handleObj.namespace)) { 46 event.handleObj = handleObj; 47 event.data = handleObj.data; 48 49 // 嘗試經過事件特例觸發handle方法,若是沒有則觸發handleObj的handler方法 50 // mouseenter/mouseleave事件特例就是使用了該handle方法, 51 // 事件特例的handle方法就是至關於一個裝飾者, 52 // 把handleObj.handler包裝了起來 53 ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem, args); 54 55 // 若是ret有值且是false則阻止默認行爲和冒泡 56 // 即return false的時候阻止默認行爲和冒泡 57 if (ret !== undefined) { 58 if ((event.result = ret) === false) { 59 event.preventDefault(); 60 event.stopPropagation(); 61 } 62 } 63 } 64 } 65 } 66 67 // 運行postDispatch鉤子方法 68 if (special.postDispatch) { 69 special.postDispatch.call(this, event); 70 } 71 72 return event.result; 73 }, 74 // 處理委託事件的方法,返回一個隊列,隊列中每一個元素有當前元素和匹配到的handler 75 handlers: function(event, handlers) { 76 var sel, handleObj, matches, i, 77 handlerQueue = [], 78 delegateCount = handlers.delegateCount, 79 // 當前時間元素 80 cur = event.target; 81 82 // 是否有委託 83 if (delegateCount && cur.nodeType && (!event.button || event.type !== 'click')) { 84 // 遍歷父輩元素,直到找到委託元素this 85 for (; cur != this; cur = cur.parentNode || this) { 86 // 確保是元素且未禁用或者非點擊事件 87 if (cur.nodeType === 1 && (cur.disabled !== true || event.type !== 'click')) { 88 matches = []; 89 // 遍歷被委託事件處理程序,handlers[i]爲jq的handler對象 90 for (i = 0; i < delegateCount; i++) { 91 handleObj = handlers[i]; 92 93 // 當前handler的選擇器字符, 加空格字符串是爲了防止和Object.prototype屬性衝突 94 sel = handleObj.selector + ' '; 95 96 // matches[sel]保存着當前元素是否在受委託元素中的標記 97 if (matches[sel] === undefined) { 98 matches[sel] = handleObj.needsContext ? 99 jQuery(sel, this).index(cur) >= 0 : 100 jQuery.find(sel, this, null, [cur]).length; 101 } 102 // 若是當前元素是在受委託元素中,則將當前handlerObj推入到matches數組中 103 if (matches[sel]) { 104 matches.push(handleObj); 105 } 106 } 107 // 若是matches數組有內容,則將新對象推入handlerQueue隊列中 108 // elem保存着當前元素,handlers這保存着當前元素匹配的handlers 109 if (matches.length) { 110 handlerQueue.push({ 111 elem: cur, 112 handlers: matches 113 }); 114 } 115 } 116 } 117 } 118 119 // 若是handlers還有剩餘,把剩餘的部分也推入到隊列中 120 if (delegateCount < handlers.length) { 121 handlerQueue.push({ 122 elem: this, 123 handlers: handlers.slice(delegateCount) 124 }); 125 } 126 127 return handlerQueue; 128 }, 129 // 建立一個jq event對象,讓其擁有和原始event同樣的屬性和值 130 fix: function(event) { 131 if (event[jQuery.expando]) { 132 return event; 133 } 134 135 var i, prop, copy, 136 type = event.type, 137 originalEvent = event, 138 fixHook = this.fixHooks[type]; 139 140 // 若是fixHook不存在判斷是鼠標事件仍是鍵盤事件再指向相應的鉤子對象 141 if (!fixHook) { 142 this.fixHooks[type] = fixHook = 143 rmouseEvent.test(type) ? this.mouseHooks : 144 rkeyEvent.test(type) ? this.keyHooks : {}; 145 } 146 // fixHook是否有props屬性,該值是一個數組,若是有則添加到jQuery.event.props中 147 copy = fixHook.props ? this.props.concat(fixHook.props) : this.props; 148 // 建立一個jQuery Event實例event,默認行爲和冒泡fix 149 event = new jQuery.Event(originalEvent); 150 151 // 給jq event添加原始event對象的屬性 152 i = copy.length; 153 while (i--) { 154 prop = copy[i]; 155 event[prop] = originalEvent[prop]; 156 } 157 158 // Support: IE<9 159 if (!event.target) { 160 event.target = originalEvent.srcElement || document; 161 } 162 163 // Support: Chrome 23+, Safari? 164 if (event.target.nodeType === 3) { 165 event.target = event.target.parentNode; 166 } 167 168 // Support: IE<9 169 event.metaKey = !! event.metaKey; 170 171 // 若是鉤子對象有filter解決兼容方法,則返回filter後的event 172 return fixHook.filter ? fixHook.filter(event, originalEvent) : event; 173 },
jQuery.event.dispatch的任務主要是:
首先經過jQuery.event.fix(event)建立jQuery的event對象來代理訪問原生的event,jQuery.event.fix這個方法會對event作兼容處理。
而後經過jQuery.event.handlers計算出委託事件處理隊列handlerQueue(冒泡路徑上的元素),沒有委託則保存着當前元素和保存着其事件處理相關信息的對象handleObj。遍歷委託事件處理隊列,再遍歷事件處理數組,找到匹配的事件類型,若是有處理程序,就執行它。可使用event.stopImmediatePropagation()來阻止遍歷下一個事件處理數組項。若是當前元素的當前事件處理程序返回值是false或者內部使用了event.stopPropagation()。就不會遍歷下一個冒泡路徑上的元素了(即當前元素的父級上的元素)。
jQuery.event.special[event.type].preDispatch和jQuery.event.special[event.type].postDispatch分別是派遣事件開始和結束的鉤子方法
而後是手動觸發事件$.fn.trigger:
1 /** 2 * 1.可觸發自定義事件 3 * 2.觸發原生事件處理程序 4 * 1).經過jQuery定義的 5 * 2).若是觸發該類型事件都會觸發elem[type]和elem['on' + type]方法,若是沒有冒泡阻止,也會觸發其餘冒泡路徑上的元素的ontype方法 6 * 7 * @param event 8 * @param data 9 * @param elem 10 * @param onlyHandlers 11 * @returns {*} 12 */ 13 trigger: function(event, data, elem, onlyHandlers) { 14 var handle, ontype, cur, 15 bubbleType, special, tmp, i, 16 eventPath = [elem || document], 17 type = core_hasOwn.call(event, 'type') ? event.type : event, 18 namespaces = core_hasOwn.call(event, 'namespace') ? event.namespace.split('.') : []; 19 20 cur = tmp = elem = elem || document; 21 22 if (elem.nodeType === 3 || elem.nodeType === 8) { 23 return; 24 } 25 26 // focus/blur變形爲focusin/out,確保咱們不會馬上觸發它們 27 if (rfocusMorph.test(type + jQuery.event.triggered)) { 28 return; 29 } 30 31 if (type.indexOf('.') >= 0) { 32 namespaces = type.split('.'); 33 // 取出第一項,事件類型 34 type = namespaces.shift(); 35 // 命名空間排序 36 namespaces.sort(); 37 } 38 ontype = type.indexOf(':') < 0 && 'on' + type; 39 40 // 確保是jQuery的event對象 41 event = event[jQuery.expando] ? 42 event : 43 new jQuery.Event(type, typeof event === 'object' && event); 44 45 event.isTrigger = true; 46 event.namespace = namespaces.join('.'); 47 event.namespace_re = event.namespace ? 48 new RegExp('(^|\\.)' + namespaces.join('\\.(?:.*\\.|)') + '(\\.|$)') : 49 null; 50 51 // 清除事件,防止被重用 52 event.result = undefined; 53 if (!event.target) { 54 event.target = elem; 55 } 56 57 // 克隆來源數據和預先準備事件,建立處理程序參數列表 58 data = data == null ? 59 [event] : 60 jQuery.makeArray(data, [event]); 61 62 // 特殊的狀況下的trigger 63 special = jQuery.event.special[type] || {}; 64 if (!onlyHandlers && special.trigger && special.trigger.apply(elem, data) === false) { 65 return; 66 } 67 68 // 保存冒泡時通過的元素到eventPath中,向上冒到document,而後到window;也多是全局ownerDocument變量 69 if (!onlyHandlers && !special.noBubble && !jQuery.isWindow(elem)) { 70 bubbleType = special.delegateType || type; 71 if (!rfocusMorph.test(bubbleType + type)) { 72 // 若是不是focus/blur類型,將當前元素改成父節點元素 73 cur = cur.parentNode; 74 } 75 // 一直向上獲取父輩元素並存入eventPath數組中 76 for (; cur; cur = cur.parentNode) { 77 eventPath.push(cur); 78 tmp = cur; 79 } 80 81 // 如tmp到了document,咱們添加window對象 82 if (tmp === (elem.ownerDocument || document)) { 83 eventPath.push(tmp.defaultView || tmp.parentWindow || window); 84 } 85 } 86 87 // 在事件路徑上觸發處理程序, 若是沒有阻止冒泡就會遍歷eventPath, 88 // 若是當前元素對應的事件類型有事件處理程序,就執行它,直到到最頂元素。 89 // 若是阻止,在第一次遍歷後就不會再遍歷了。 90 i = 0; 91 while ((cur = eventPath[i++]) && !event.isPropagationStopped()) { 92 event.type = i > 1 ? 93 bubbleType : 94 special.bindType || type; 95 96 // jQuery 緩存中的處理程序 97 handle = (jQuery._data(cur, 'events') || {})[event.type] && jQuery._data(cur, 'handle'); 98 // 若是有handle方法,執行它。這裏的handle是元素綁定的事件 99 if (handle) { 100 handle.apply(cur, data); 101 } 102 103 // 觸發原生處理程序 104 handle = ontype && cur[ontype]; 105 if (handle && jQuery.acceptData(cur) && handle.apply && handle.apply(cur, data) === false) { 106 event.preventDefault(); 107 } 108 } 109 event.type = type; 110 111 // 若是沒有阻止默認行爲動做,處理elem的type屬性事件, 112 // 執行elem[type]處理程序但不會觸發elem['on' + type] 113 if (!onlyHandlers && !event.isDefaultPrevented()) { 114 // 1. 115 // 1).沒有special._default 116 // 2).有special._default,該方法的執行結果返回false 117 // 2. 118 // type不能使click且elem不能使a標籤 119 // 3. 120 // elem可接受緩存 121 if ((!special._default || special._default.apply(elem.ownerDocument, data) === false) && !(type === 'click' && jQuery.nodeName(elem, 'a')) && jQuery.acceptData(elem)) { 122 123 if (ontype && elem[type] && !jQuery.isWindow(elem)) { 124 // 緩存older 125 tmp = elem[ontype]; 126 127 // 當咱們執行foo()時,不會從新觸發onfoo事件 128 if (tmp) { 129 elem[ontype] = null; 130 } 131 132 // 防止再次觸發中的相同事件,第一次觸發完後jQuery.event.triggered = undefined 133 jQuery.event.triggered = type; 134 try { 135 // 執行方法 136 elem[type](); 137 } catch (e) { 138 // 隱藏元素在focus/blur時,ie9如下會奔潰 139 } 140 jQuery.event.triggered = undefined; 141 142 if (tmp) { 143 elem[ontype] = tmp; 144 } 145 } 146 } 147 } 148 149 return event.result; 150 },
trigger會建立一個新的jQuery Event對象,添加一些trigger的附加屬性,onlyHandlers和!onlyHandlers參數表明triggerHandler和trigger的區別。
1.trigger會先採集冒泡路徑上的元素保存到eventPath數組中,
2.在沒有阻止冒泡的狀況下,而後遍歷eventPath,找到對應的咱們註冊的事件處理程序,這裏分兩種事件處理,jQuery方式添加的還有原生elem['on' + type]形式添加的,這個過程都會觸發前面兩種事件處理程序。
3.在沒有阻止默認行爲的狀況下,而後就是執行當前元素的elem[type]方式的事件處理程序,這種方式的事件處理程序是經過調用原生的事件註冊addEventListener/attachEvent(因此若是沒阻止冒泡,它就會向上冒泡了),固然這個步驟要避免觸發上一步的事件程序,即jQuery的原生註冊接口和ontype形式的,經過jQuery.event.triggered來保存已經被觸發了的標誌,這樣jQuery的原生註冊接口經過判斷jQuery.event.triggered來決定是否觸發。而ontype形式的就先把ontype至爲null,執行完操做後再恢復。
。
而triggerHandler就只作了trigger過程當中的第二步,只是eventPath之保存了一個元素,就是當前元素.
最後是註銷事件$.fn.off
1 off: function(types, selector, fn) { 2 var handleObj, type; 3 // 當傳遞的types是jQuery建立的event對象時 4 if (types && types.preventDefault && types.handleObj) { 5 // ( event ) dispatched jQuery.Event 6 handleObj = types.handleObj; 7 jQuery(types.delegateTarget).off( 8 handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, 9 handleObj.selector, 10 handleObj.handler 11 ); 12 return this; 13 } 14 // 當types是對象,遍歷遞歸 15 if (typeof types === "object") { 16 // ( types-object [, selector] ) 17 for (type in types) { 18 this.off(type, selector, types[type]); 19 } 20 return this; 21 } 22 if (selector === false || typeof selector === "function") { 23 // ( types [, fn] ) 24 fn = selector; 25 selector = undefined; 26 } 27 if (fn === false) { 28 fn = returnFalse; 29 } 30 // 統一調用jQuery.event.remove移除事件處理程序及相關信息 31 return this.each(function() { 32 jQuery.event.remove(this, types, fn, selector); 33 }); 34 },
$.fn.off實際上調用的是jQuery.event.remove這個方法:
1 /** 2 * 註銷元素的事件或者事件集 3 * 4 * 經過jQuery.event.remove實現,其執行過程大體以下: 5 1. 現調用jQuery._data從緩存$.cache中取出elem對應的全部數組(內部數據,與調用jQuery.data存儲的數據稍有不一樣 6 2. 若是未傳入types則移除全部事件句柄,若是types是命名空間,則移除全部與命名空間匹配的事件句柄 7 3. 若是是多個事件,則分割後遍歷 8 4. 若是未指定刪除哪一個事件句柄,則刪除事件類型對應的所有句柄,或者與命名空間匹配的所有句柄 9 5. 若是指定了刪除某個事件句柄,則刪除指定的事件句柄 10 6. 全部的事件句柄刪除,都直接在事件句柄數組jQuery._data( elem ).events[ type ]上調用splice操做 11 7. 最後檢查事件句柄數組的長度,若是爲0,或爲1但要刪除,則移除綁定在elem上DOM事件 12 8. 最後的最後,若是elem對應的全部事件句柄events都已刪除,則從緩存中移走elem的內部數據 13 9. 在以上的各個過程,都要檢查是否有特例須要處理 14 */ 15 remove: function(elem, types, handler, selector, mappedTypes) { 16 var j, handleObj, tmp, 17 origCount, t, events, 18 special, handlers, type, 19 namespaces, origType, 20 elemData = jQuery.hasData(elem) && jQuery._data(elem); 21 22 if (!elemData || !(events = elemData.events)) { 23 return; 24 } 25 26 types = (types || '').match(core_rnotwhite) || ['']; 27 t = types.length; 28 while (t--) { 29 tmp = rtypenamespace.exec(types[t]) || []; 30 type = origType = tmp[1]; 31 namespaces = (tmp[2] || '').split('.').sort(); 32 33 // 若是沒有指定type,解綁元素的全部事件(包括命名空間上的) 34 if (!type) { 35 for (type in events) { 36 jQuery.event.remove(elem, type + types[t], handler, selector, true); 37 } 38 continue; 39 } 40 41 special = jQuery.event.special[type] || {}; 42 type = (selector ? special.delegateType : special.bindType) || type; 43 // 該事件列表 44 handlers = events[type] || []; 45 tmp = tmp[2] && new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)"); 46 47 // 刪除匹配的事件 48 49 // 事件列表的長度 50 origCount = j = handlers.length; 51 while (j--) { 52 handleObj = handlers[j]; 53 54 if ((mappedTypes || origType === handleObj.origType) && 55 (!handler || handler.guid === handleObj.guid) && 56 (!tmp || tmp.test(handleObj.namespace)) && 57 (!selector || selector === handleObj.selector || selector === "**" && handleObj.selector)) { 58 // 刪除events事件列表中的該項 59 handlers.splice(j, 1); 60 // 若是有委託,delegateCount就減一 61 if (handleObj.selector) { 62 handlers.delegateCount--; 63 } 64 if (special.remove) { 65 special.remove.call(elem, handleObj); 66 } 67 } 68 } 69 70 // 刪除通用的事件處理程序,同時避免無限遞歸 71 72 // 若是原始事件列表有項,通過前面的步驟長度爲0 73 if (origCount && !handlers.length) { 74 if (!special.teardown || special.teardown.call(elem, namespaces, elemData.handle) === false) { 75 // 刪除註冊的偵聽事件 76 jQuery.removeEvent(elem, type, elemData.handle); 77 } 78 79 // 刪除events[type]屬性 80 delete events[type]; 81 } 82 } 83 84 // 若是events再也不使用則刪除 85 if (jQuery.isEmptyObject(events)) { 86 delete elemData.handle; 87 88 // 使用removeData檢查空的和清空expando 89 jQuery._removeData(elem, 'events'); 90 } 91 },
註銷嘛,就是對咱們保存在緩存系統中的對應數據進行銷燬,這裏不贅述了。
總結:
jQuery的event模塊很是的強大,我也只是講了通常流程,它還有一些鉤子對象處理瀏覽器兼容問題,我這裏就不探討了。但願個人講解能夠令你解惑。