Skip to main content
 首页 » 编程设计

深入理解jQuery的Event机制

2022年07月16日143落叶无声

jQuery的Event模块非常强大。其功能远远比原生事件监听器强大许多,对同一个元素的监听只用一个eventListener,内部则是一个强大的观察者,根据匹配事件类型触发相应回调。jQuery不仅封装了兼容性差异,还提供了命名空间式注册注销事件,灵活的事件委托(事件代理),手动触发事件trigger以及自定义事件。因为jQuery提供的bind,delegate,live(1.9版本废除了)的功能都是通过on来适配的,所以这里只讲on,off,trigger。

1.注册事件$.fn.on方法

 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         }
View Code

可以从源码看出前面都是针对其他高层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         },
View Code

该方法会先从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     });
View Code

jQuery缓存系统中对应的事件处理器数据结构:

jQuery.event.add就是组装上面的数据结构

2是当前元素对应的缓存id,该对象下一级有两个对象events和handle,events保存着事件处理器,handle就是该元素对应的唯一一个原生事件监听处理程序的回调。

events里面保存着我们注册事件时的事件类型key对应的事件处理程序回调列表,回调列表里面的每一项保存着当前事件的信息,handler就是我们注册时的回调。我们还看到每个回调列表都会保存着一个delegateCount属性,这是jQuery计算出委托事件数目,如果用了委托注册,jQuery先会遍历你的事件类型,如果只有一个delegateCount就为1,否则就为对应的事件类型个数。delegateCount在后面触发事件时要用到。

既然已经注册好了,我们要蓄势待发了,接着是用户触发事件(用户行为的触发事件,非手动触发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         },
View Code

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         },
View Code

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         },
View Code

$.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         },
View Code

注销嘛,就是对我们保存在缓存系统中的对应数据进行销毁,这里不赘述了。

总结:

jQuery的event模块非常的强大,我也只是讲了一般流程,它还有一些钩子对象处理浏览器兼容问题,我这里就不探讨了。希望我的讲解可以令你解惑。


本文参考链接:https://www.cnblogs.com/webFrontDev/p/3509775.html