深刻理解jQuery的Event機制

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模塊很是的強大,我也只是講了通常流程,它還有一些鉤子對象處理瀏覽器兼容問題,我這裏就不探討了。但願個人講解能夠令你解惑。

相關文章
相關標籤/搜索