深入理解jQuery的Event机制
2019独角兽企业重金招聘Python工程师标准>>>
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 }
可以从源码看出前面都是针对其他高层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.f81 namespaces = (tmp[2] || '').split('.').sort();82 83 // 事件特例(就是为一些事件类型的一些特殊情况的处理)84 special = jQuery.event.special[type] || {};85 86 // 如果有事件特例,就使用。否则还是使用原始type87 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缓存系统中对应的事件处理器数据结构:
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的event12 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的event23 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 // 处理委托事件的方法,返回一个队列,队列中每个元素有当前元素和匹配到的handler75 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 // 遍历父辈元素,直到找到委托元素this85 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 event8 * @param data9 * @param elem10 * @param onlyHandlers11 * @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 // 特殊的情况下的trigger63 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.Event6 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模块非常的强大,我也只是讲了一般流程,它还有一些钩子对象处理浏览器兼容问题,我这里就不探讨了。希望我的讲解可以令你解惑。
转载于:https://my.oschina.net/u/3358860/blog/3049230
深入理解jQuery的Event机制相关推荐
- 我理解的Hanlder--android消息传递机制
每一个学习Android的同学都会觉得Handler是一个神奇的东西,我也一样,开始我以为我懂了Handler的机制,后来发现自己是一知半解,昨天想想,我能否自己实现一个Handler,让子线程与Ac ...
- 深入理解jQuery中的Deferred
深入理解jQuery中的Deferred 引入 1 在开发的过程中,我们经常遇到某些耗时很长的javascript操作,并且伴随着大量的异步. 2 比如我们有一个ajax的操作,这个ajax从发出 ...
- 甘利俊一 | 信息几何法:理解深度神经网络学习机制的重要工具
智源导读:深度学习的统计神经动力学主要涉及用信息几何的方法对深度随机权值网络进行研究.深度学习技术近年来在计算机视觉.语音识别等任务取得了巨大成功,但是其背后的数学理论发展却很滞后.日本理化所的Shu ...
- 深入理解js的执行机制
写在前面 javascript在浏览器中被浏览器的js引擎执行解释,从执行上下文的角度分析一下js的执行机制 执行上下文 执行上下文被定义成javascript引擎在处理理解js代码时,所创建的一个动 ...
- (转载)彻底理解浏览器的缓存机制
彻底理解浏览器的缓存机制 2018/04/16 概述 浏览器的缓存机制也就是我们说的HTTP缓存机制,其机制是根据HTTP报文的缓存标识进行的,所以在分析浏览器缓存机制之前,我们先使用图文简单介绍一下 ...
- mysql 锁机制 mvcc_轻松理解MYSQL MVCC 实现机制
轻松理解MYSQL MVCC 实现机制 轻松理解MYSQL MVCC 实现机制 #### 1. MVCC简介 ##### 1.1 什么是MVCC MVCC是一种多版本并发控制机制. ##### 1.2 ...
- 深入BCB理解VCL的消息机制
深入BCB理解VCL的消息机制 引子:本文所谈及的技术内容都来自于Internet的公开信息.由笔者在闲暇之际整理 后,贴出来以飴网友,姑且妄称原创.每次在国外网站上找到精彩文章的时候,心中都 会暗自 ...
- 深入理解 Java 垃圾回收机制
转载自 http://www.cnblogs.com/andy-zcx/p/5522836.html 深入理解 Java 垃圾回收机制 一:垃圾回收机制的意义 java 语言中一个显著的特点就是引入 ...
- 轻松理解—继承成员访问控制机制
在我们学习面向对象程序设计的时候,那么这个继承成员访问控制机制您必须对其有深入的了解,达到熟练掌握的目的:要不也许这点知识你不过关,你对这点知识还抱着半信半疑的感觉,那么你一旦碰到这个问题,你的第一个 ...
最新文章
- mysql date(6)_不使用MySQL中的DATE_ADD()将6个小时添加到now()函数吗?
- gm怎么刷东西 rust_刷了这种黑板漆再也不用担心吃粉笔灰了
- esxi 部署模板_vSphere使用模板部署虚拟机
- 3.Redis与python交互
- Leetcode —— 面试题 04.02. 最小高度树(Python)
- PHP 开发邀请功能,使用 larainvite 为 Laravel 5.3 应用添加邀请注册功能
- 如果把Python代码写成这样子就太难看了
- protect db by denying DDL operations
- 电脑如何测网速_职场人必备?告别加班的软件,100%提升工作效率|电脑|程序员|mac...
- 【PDF】PDF文件分页拆分(免费方法)
- [转]Excel插件开发基础知识
- 挑战程序竞赛系列(22):3.2弹性碰撞
- 密码学三大顶会和信息安全四大顶会网址
- 电视剧《都挺好》弹幕数据分析
- linux学习(跟着b站尚硅谷老师学习)
- 常用类库-java.lang.String
- Win10安装安卓模拟器入坑记
- select UNION ALL 合并两张表数据
- 合并bn层到conv或FC层原理介绍及代码实现
- Chat GPT5的主要介绍