Skip to main content
 首页 » 编程设计

jQuery源码分析之Events模块

2022年07月16日27小虾米
   1 var rformElems = /^(?:input|select|textarea)$/i, 
   2         rkeyEvent = /^key/, 
   3         rmouseEvent = /^(?:mouse|contextmenu)|click/, 
   4         rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, 
   5         rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; 
   6  
   7     function returnTrue() { 
   8         return true; 
   9     } 
  10  
  11     function returnFalse() { 
  12         return false; 
  13     } 
  14  
  15     jQuery.event = { 
  16         global: {}, 
  17         /** 
  18          * 事件绑定最后都通过jQuery.event.add来实现。其执行过程大致如下: 
  19          1.  先调用jQuery._data从$.cache中取出已有的事件缓存(私有数据,Cache的解析详见数据缓存) 
  20          2.  如果是第一次在DOM元素上绑定该类型事件句柄,在DOM元素上绑定jQuery.event.handle,作为统一的事件响应入口 
  21          3.  将封装后的事件句柄放入缓存中 
  22          传入的事件句柄,会被封装到对象handleObj的handle属性上,此外handleObj还会填充guid、type、namespace、data属性;DOM事件句柄elemData.handle指向jQuery.event.handle,即jQuery在DOM元素上绑定事件时总是绑定同样的DOM事件句柄jQuery.event.handle。 
  23          事件句柄在缓存$.cache中的数据结构如下,事件类型和事件句柄都存储在属性events中,属性handle存放的执行这些事件句柄的DOM事件句柄: 
  24          elemData = { 
  25     events: { 
  26         'click' : [ 
  27             { guid: 5, type: 'click', namespace: '', data: undefined, 
  28                 handle: { guid: 5, prototype: {} } 
  29             }, 
  30             { ... } 
  31         ], 
  32         'keypress' : [ ... ] 
  33     }, 
  34     handle: { // DOM事件句柄 
  35         elem: elem, 
  36         prototype: {} 
  37     } 
  38 } 
  39          */ 
  40         add: function(elem, types, handler, data, selector) { 
  41             var tmp, events, t, handleObjIn, 
  42                 special, eventHandle, handleObj, 
  43                 handlers, type, namespaces, origType, 
  44                 // 创建或获取私有的缓存数据 
  45                 elemData = jQuery._data(elem); 
  46  
  47             if (!elemData) { 
  48                 return; 
  49             } 
  50  
  51             // 可以给jq的handler对象传参数配置 
  52             if (handler.handler) { 
  53                 handleObjIn = handler; 
  54                 handler = handleObjIn.handler; 
  55                 selector = handleObjIn.selector; 
  56             } 
  57  
  58             // 确保处理程序有唯一ID,以便查找和删除 
  59             // handler函数添加guid属性 
  60             if (!handler.guid) { 
  61                 handler.guid = jQuery.guid++; 
  62             } 
  63  
  64             // 首次初始化元素的事件结构和主要处理程序 
  65             // 缓存数据elemData添加events属性对象 
  66             if (!(events = elemData.events)) { 
  67                 events = elemData.events = {}; 
  68             } 
  69             // elemData添加handle方法 
  70             if (!(eventHandle = elemData.handle)) { 
  71                 // 当我们使用jQuery为元素添加事件处理程序时, 
  72                 // 实际上就是调用了这个通过包装的函数, 
  73                 // 而这里面就是通过jQuery.event.dispatch方法来触发的 
  74                 eventHandle = elemData.handle = function(e) { 
  75                     // 如果jQuery完成初始化且不存在e或者已经jQuery.event.trigger()了 
  76                     // 返回派遣委托后的结果 
  77                     // this指向eventHandle.elem,解决ie中注册事件this指向的问题 
  78                     // 如果是IE,这里使用attachEvent监听,其事件处理程序的第一个参数就有ie的event了。 
  79                     // 平时说的window.event是指在elem['on' + type] = handler;的情况 
  80                     return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply(eventHandle.elem, arguments) : 
  81                         undefined; 
  82                 }; 
  83                 // 给handle函数添加elem属性防止IE非原生内存泄露 
  84                 // handle方法添加elem属性 
  85                 eventHandle.elem = elem; 
  86             } 
  87  
  88             // 处理空格分离的多事件 
  89             // jQuery(...).bind("mouseover mouseout", fn); 
  90             types = (types || '').match(core_rnotwhite) || ['']; 
  91             t = types.length; 
  92             while (t--) { 
  93                 tmp = rtypenamespace.exec(types[t]) || []; 
  94                 type = origType = tmp[1]; 
  95                 // 对命名空间进行排序 
  96                 // click.a.c.f.d --- a.c.d.f 
  97                 namespaces = (tmp[2] || '').split('.').sort(); 
  98  
  99                 // 事件特例(就是为一些事件类型的一些特殊情况的处理) 
 100                 special = jQuery.event.special[type] || {}; 
 101  
 102                 // 如果有事件特例,就使用。否则还是使用原始type 
 103                 type = (selector ? special.delegateType : special.bindType) || type; 
 104  
 105                 // 更新事件特例的类型 
 106                 special = jQuery.event.special[type] || {}; 
 107  
 108                 // 给handleObj添加事件处理程序相关信息, 
 109                 // 如果target对象有相同属性或方法则替换为handleObj的 
 110                 handleObj = jQuery.extend({ 
 111                     type: type, 
 112                     origType: origType, 
 113                     data: data, 
 114                     handler: handler, 
 115                     guid: handler.guid, 
 116                     selector: selector, 
 117                     needsContext: selector && jQuery.expr.match.needsContext.test(selector), 
 118                     namespace: namespaces.join('.') 
 119                 }, handleObjIn); 
 120  
 121                 // 首次初始化事件处理程序队列 
 122                 if (!(handlers = events[type])) { 
 123                     handlers = events[type] = []; 
 124                     handlers.delegateCount = 0; 
 125  
 126                     // 当事件特例处理程序没有setup方法或者setup返回false时使用addEventListener/attachEvent 
 127                     if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) { 
 128                         // 给元素绑定事件处理程序,知道这里才真正添加事件处理程序 
 129                         if (elem.addEventListener) { 
 130                             elem.addEventListener(type, eventHandle, false); 
 131                         } else if (elem.attachEvent) { 
 132                             elem.attachEvent('on' + type, eventHandle); 
 133                         } 
 134                     } 
 135                 } 
 136  
 137                 // 事件特例的一些处理 
 138                 if (special.add) { 
 139                     special.add.call(elem, handleObj); 
 140  
 141                     if (!handleObj.handler.guid) { 
 142                         handleObj.handler.guid = handler.guid; 
 143                     } 
 144                 } 
 145  
 146                 // 添加元素的事件处理列表, 
 147                 // 如果有selector,则用来给委托事件使用的 
 148                 if (selector) { 
 149                     handlers.splice(handlers.delegateCount++, 0, handleObj); 
 150                 } else { 
 151                     handlers.push(handleObj); 
 152                 } 
 153  
 154                 // 追踪哪个事件曾经被运行过 
 155                 jQuery.event.global[type] = true; 
 156             } 
 157  
 158             // 防止IE内存泄露 
 159             elem = null; 
 160         }, 
 161         /** 
 162          * 注销元素的事件或者事件集 
 163          * 
 164          * 通过jQuery.event.remove实现,其执行过程大致如下: 
 165          1. 现调用jQuery._data从缓存$.cache中取出elem对应的所有数组(内部数据,与调用jQuery.data存储的数据稍有不同 
 166          2. 如果未传入types则移除所有事件句柄,如果types是命名空间,则移除所有与命名空间匹配的事件句柄 
 167          3. 如果是多个事件,则分割后遍历 
 168          4. 如果未指定删除哪个事件句柄,则删除事件类型对应的全部句柄,或者与命名空间匹配的全部句柄 
 169          5. 如果指定了删除某个事件句柄,则删除指定的事件句柄 
 170          6. 所有的事件句柄删除,都直接在事件句柄数组jQuery._data( elem ).events[ type ]上调用splice操作 
 171          7. 最后检查事件句柄数组的长度,如果为0,或为1但要删除,则移除绑定在elem上DOM事件 
 172          8. 最后的最后,如果elem对应的所有事件句柄events都已删除,则从缓存中移走elem的内部数据 
 173          9. 在以上的各个过程,都要检查是否有特例需要处理 
 174          */ 
 175         remove: function(elem, types, handler, selector, mappedTypes) { 
 176             var j, handleObj, tmp, 
 177                 origCount, t, events, 
 178                 special, handlers, type, 
 179                 namespaces, origType, 
 180                 elemData = jQuery.hasData(elem) && jQuery._data(elem); 
 181  
 182             if (!elemData || !(events = elemData.events)) { 
 183                 return; 
 184             } 
 185  
 186             types = (types || '').match(core_rnotwhite) || ['']; 
 187             t = types.length; 
 188             while (t--) { 
 189                 tmp = rtypenamespace.exec(types[t]) || []; 
 190                 type = origType = tmp[1]; 
 191                 namespaces = (tmp[2] || '').split('.').sort(); 
 192  
 193                 // 如果没有指定type,解绑元素的所有事件(包括命名空间上的) 
 194                 if (!type) { 
 195                     for (type in events) { 
 196                         jQuery.event.remove(elem, type + types[t], handler, selector, true); 
 197                     } 
 198                     continue; 
 199                 } 
 200  
 201                 special = jQuery.event.special[type] || {}; 
 202                 type = (selector ? special.delegateType : special.bindType) || type; 
 203                 // 该事件列表 
 204                 handlers = events[type] || []; 
 205                 tmp = tmp[2] && new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)"); 
 206  
 207                 // 删除匹配的事件 
 208  
 209                 // 事件列表的长度 
 210                 origCount = j = handlers.length; 
 211                 while (j--) { 
 212                     handleObj = handlers[j]; 
 213  
 214                     if ((mappedTypes || origType === handleObj.origType) && 
 215                         (!handler || handler.guid === handleObj.guid) && 
 216                         (!tmp || tmp.test(handleObj.namespace)) && 
 217                         (!selector || selector === handleObj.selector || selector === "**" && handleObj.selector)) { 
 218                         // 删除events事件列表中的该项 
 219                         handlers.splice(j, 1); 
 220                         // 如果有委托,delegateCount就减一 
 221                         if (handleObj.selector) { 
 222                             handlers.delegateCount--; 
 223                         } 
 224                         if (special.remove) { 
 225                             special.remove.call(elem, handleObj); 
 226                         } 
 227                     } 
 228                 } 
 229  
 230                 // 删除通用的事件处理程序,同时避免无限递归 
 231  
 232                 // 如果原始事件列表有项,经过前面的步骤长度为0 
 233                 if (origCount && !handlers.length) { 
 234                     if (!special.teardown || special.teardown.call(elem, namespaces, elemData.handle) === false) { 
 235                         // 删除注册的侦听事件 
 236                         jQuery.removeEvent(elem, type, elemData.handle); 
 237                     } 
 238  
 239                     // 删除events[type]属性 
 240                     delete events[type]; 
 241                 } 
 242             } 
 243  
 244             // 如果events不再使用则删除 
 245             if (jQuery.isEmptyObject(events)) { 
 246                 delete elemData.handle; 
 247  
 248                 // 使用removeData检查空的和清空expando 
 249                 jQuery._removeData(elem, 'events'); 
 250             } 
 251         }, 
 252         /** 
 253          * 
 254          * 
 255          * 1.可触发自定义事件 
 256          * 2.触发原生事件处理程序 
 257          * 1).通过jQuery定义的 
 258          * 2).如果触发该类型事件都会触发elem[type]和elem['on' + type]方法,如果没有冒泡阻止,也会触发其他冒泡路径上的元素的ontype方法 
 259          * 
 260          * @param event 
 261          * @param data 
 262          * @param elem 
 263          * @param onlyHandlers 
 264          * @returns {*} 
 265          */ 
 266         trigger: function(event, data, elem, onlyHandlers) { 
 267             var handle, ontype, cur, 
 268                 bubbleType, special, tmp, i, 
 269                 eventPath = [elem || document], 
 270                 type = core_hasOwn.call(event, 'type') ? event.type : event, 
 271                 namespaces = core_hasOwn.call(event, 'namespace') ? event.namespace.split('.') : []; 
 272  
 273             cur = tmp = elem = elem || document; 
 274  
 275             if (elem.nodeType === 3 || elem.nodeType === 8) { 
 276                 return; 
 277             } 
 278  
 279             // focus/blur变形为focusin/out,确保我们不会立刻触发它们 
 280             if (rfocusMorph.test(type + jQuery.event.triggered)) { 
 281                 return; 
 282             } 
 283  
 284             if (type.indexOf('.') >= 0) { 
 285                 namespaces = type.split('.'); 
 286                 // 取出第一项,事件类型 
 287                 type = namespaces.shift(); 
 288                 // 命名空间排序 
 289                 namespaces.sort(); 
 290             } 
 291             ontype = type.indexOf(':') < 0 && 'on' + type; 
 292  
 293             // 确保是jQuery的event对象 
 294             event = event[jQuery.expando] ? 
 295                 event : 
 296                 new jQuery.Event(type, typeof event === 'object' && event); 
 297  
 298             event.isTrigger = true; 
 299             event.namespace = namespaces.join('.'); 
 300             event.namespace_re = event.namespace ? 
 301                 new RegExp('(^|\\.)' + namespaces.join('\\.(?:.*\\.|)') + '(\\.|$)') : 
 302                 null; 
 303  
 304             // 清除事件,防止被重用 
 305             event.result = undefined; 
 306             if (!event.target) { 
 307                 event.target = elem; 
 308             } 
 309  
 310             // 克隆来源数据和预先准备事件,创建处理程序参数列表 
 311             data = data == null ? 
 312                 [event] : 
 313                 jQuery.makeArray(data, [event]); 
 314  
 315             // 特殊的情况下的trigger 
 316             special = jQuery.event.special[type] || {}; 
 317             if (!onlyHandlers && special.trigger && special.trigger.apply(elem, data) === false) { 
 318                 return; 
 319             } 
 320  
 321             // 保存冒泡时经过的元素到eventPath中,向上冒到document,然后到window;也可能是全局ownerDocument变量 
 322             if (!onlyHandlers && !special.noBubble && !jQuery.isWindow(elem)) { 
 323                 bubbleType = special.delegateType || type; 
 324                 if (!rfocusMorph.test(bubbleType + type)) { 
 325                     // 如果不是focus/blur类型,将当前元素改为父节点元素 
 326                     cur = cur.parentNode; 
 327                 } 
 328                 // 一直向上获取父辈元素并存入eventPath数组中 
 329                 for (; cur; cur = cur.parentNode) { 
 330                     eventPath.push(cur); 
 331                     tmp = cur; 
 332                 } 
 333  
 334                 // 如tmp到了document,我们添加window对象 
 335                 if (tmp === (elem.ownerDocument || document)) { 
 336                     eventPath.push(tmp.defaultView || tmp.parentWindow || window); 
 337                 } 
 338             } 
 339  
 340             // 在事件路径上触发处理程序, 如果没有阻止冒泡就会遍历eventPath, 
 341             // 如果当前元素对应的事件类型有事件处理程序,就执行它,直到到最顶元素。 
 342             // 如果阻止,在第一次遍历后就不会再遍历了。 
 343             i = 0; 
 344             while ((cur = eventPath[i++]) && !event.isPropagationStopped()) { 
 345                 event.type = i > 1 ? 
 346                     bubbleType : 
 347                     special.bindType || type; 
 348  
 349                 // jQuery 缓存中的处理程序 
 350                 handle = (jQuery._data(cur, 'events') || {})[event.type] && jQuery._data(cur, 'handle'); 
 351                 // 如果有handle方法,执行它。这里的handle是元素绑定的事件 
 352                 if (handle) { 
 353                     handle.apply(cur, data); 
 354                 } 
 355  
 356                 // 触发原生处理程序elem['on' + type] 
 357                 handle = ontype && cur[ontype]; 
 358                 if (handle && jQuery.acceptData(cur) && handle.apply && handle.apply(cur, data) === false) { 
 359                     event.preventDefault(); 
 360                 } 
 361             } 
 362             event.type = type; 
 363  
 364             // 如果没有阻止默认行为动作,处理elem的type属性事件, 
 365             // 执行elem[type]处理程序但不会触发elem['on' + type] 
 366             if (!onlyHandlers && !event.isDefaultPrevented()) { 
 367                 // 1. 
 368                 // 1).没有special._default 
 369                 // 2).有special._default,该方法的执行结果返回false 
 370                 // 2. 
 371                 // type不能使click且elem不能使a标签 
 372                 // 3. 
 373                 // elem可接受缓存 
 374                 if ((!special._default || special._default.apply(elem.ownerDocument, data) === false) && !(type === 'click' && jQuery.nodeName(elem, 'a')) && jQuery.acceptData(elem)) { 
 375  
 376                     if (ontype && elem[type] && !jQuery.isWindow(elem)) { 
 377                         // 缓存older 
 378                         tmp = elem[ontype]; 
 379  
 380                         // 当我们执行foo()时,不会重新触发onfoo事件 
 381                         if (tmp) { 
 382                             elem[ontype] = null; 
 383                         } 
 384  
 385                         // 防止再次触发中的相同事件,第一次触发完后jQuery.event.triggered = undefined 
 386                         jQuery.event.triggered = type; 
 387                         try { 
 388                             // 执行方法 
 389                             elem[type](); 
 390                         } catch (e) { 
 391                             // 隐藏元素在focus/blur时,ie9以下会奔溃 
 392                         } 
 393                         jQuery.event.triggered = undefined; 
 394  
 395                         if (tmp) { 
 396                             elem[ontype] = tmp; 
 397                         } 
 398                     } 
 399                 } 
 400             } 
 401  
 402             return event.result; 
 403         }, 
 404         /** 
 405          * 派遣事件 
 406          * 创建jQuery的event对象来代理访问原生的event, 
 407          * 通过jQuery.event.handlers计算出委托事件处理队列handlerQueue(冒泡路径上的元素),没有委托则保存着当前元素和保存着其事件处理相关信息的对象handleObj。 
 408          * 遍历委托事件处理队列,再遍历事件处理数组,找到匹配的事件类型,如果有处理程序,就执行它。可以使用event.stopImmediatePropagation()来阻止遍历下一个事件处理数组项。如果当前元素的当前事件处理程序返回值是false或者内部使用了event.stopPropagation()。就不会遍历下一个冒泡路径上的元素了(即当前元素的父级上的元素) 
 409          * jQuery.event.special[event.type].preDispatch和jQuery.event.special[event.type].postDispatch分别是派遣事件开始和结束的钩子方法。 
 410          * @param event 原生event对象 
 411          * @returns {result|*} 
 412          */ 
 413         dispatch: function(event) { 
 414             // 从原生event中创建jq的event 
 415             event = jQuery.event.fix(event); 
 416  
 417             var i, ret, handleObj, matched, j, 
 418                 handlerQueue = [], 
 419                 args = core_slice.call(arguments), 
 420                 // 获取元素在jQuery.cache中的events对象的type数组 
 421                 handlers = (jQuery._data(this, 'events') || {})[event.type] || [], 
 422                 // 事件特例 
 423                 special = jQuery.event.special[event.type] || {}; 
 424  
 425             // 将第一个event参数替换为jq的event 
 426             args[0] = event; 
 427             // 设置委托目标 
 428             event.delegateTarget = this; 
 429  
 430             // 如果存在preDispatch钩子,则运行该方法后退出 
 431             if (special.preDispatch && special.preDispatch.call(this, event) === false) { 
 432                 return; 
 433             } 
 434  
 435             // 委托事件队列 
 436             handlerQueue = jQuery.event.handlers.call(this, event, handlers); 
 437  
 438             // 先运行委托,如果阻止了冒泡就停止循环 
 439             i = 0; 
 440             while ((matched = handlerQueue[i++]) && !event.isPropagationStopped()) { 
 441                 event.currentTarget = matched.elem; 
 442  
 443                 j = 0; 
 444  
 445                 // 遍历当前元素的事件处理程序数组 
 446                 while ((handleObj = matched.handlers[j++]) && !event.isImmediatePropagationStopped()) { 
 447                     // 被触发的时间不能有命名空间或者有命名空间,且被绑定的事件是命名空间的一个子集 
 448                     if (!event.namespace_re || event.namespace_re.test(handleObj.namespace)) { 
 449                         event.handleObj = handleObj; 
 450                         event.data = handleObj.data; 
 451  
 452                         // 尝试通过事件特例触发handle方法,如果没有则触发handleObj的handler方法 
 453                         // mouseenter/mouseleave事件特例就是使用了该handle方法,  
 454                         // 事件特例的handle方法就是相当于一个装饰者, 
 455                         // 把handleObj.handler包装了起来 
 456                         ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem, args); 
 457  
 458                         // 如果ret有值且是false则阻止默认行为和冒泡 
 459                         // 即return false的时候阻止默认行为和冒泡 
 460                         if (ret !== undefined) { 
 461                             if ((event.result = ret) === false) { 
 462                                 event.preventDefault(); 
 463                                 event.stopPropagation(); 
 464                             } 
 465                         } 
 466                     } 
 467                 } 
 468             } 
 469  
 470             // 运行postDispatch钩子方法 
 471             if (special.postDispatch) { 
 472                 special.postDispatch.call(this, event); 
 473             } 
 474  
 475             return event.result; 
 476         }, 
 477         // 处理委托事件的方法,返回一个队列,队列中每个元素有当前元素和匹配到的handler 
 478         handlers: function(event, handlers) { 
 479             var sel, handleObj, matches, i, 
 480                 handlerQueue = [], 
 481                 delegateCount = handlers.delegateCount, 
 482                 // 当前时间元素 
 483                 cur = event.target; 
 484  
 485             // 是否有委托 
 486             if (delegateCount && cur.nodeType && (!event.button || event.type !== 'click')) { 
 487                 // 遍历父辈元素,直到找到委托元素this 
 488                 for (; cur != this; cur = cur.parentNode || this) { 
 489                     // 确保是元素且未禁用或者非点击事件 
 490                     if (cur.nodeType === 1 && (cur.disabled !== true || event.type !== 'click')) { 
 491                         matches = []; 
 492                         // 遍历被委托事件处理程序,handlers[i]为jq的handler对象 
 493                         for (i = 0; i < delegateCount; i++) { 
 494                             handleObj = handlers[i]; 
 495  
 496                             // 当前handler的选择器字符, 加空格字符串是为了防止和Object.prototype属性冲突 
 497                             sel = handleObj.selector + ' '; 
 498  
 499                             // matches[sel]保存着当前元素是否在受委托元素中的标记 
 500                             if (matches[sel] === undefined) { 
 501                                 matches[sel] = handleObj.needsContext ? 
 502                                     jQuery(sel, this).index(cur) >= 0 : 
 503                                     jQuery.find(sel, this, null, [cur]).length; 
 504                             } 
 505                             // 如果当前元素是在受委托元素中,则将当前handlerObj推入到matches数组中 
 506                             if (matches[sel]) { 
 507                                 matches.push(handleObj); 
 508                             } 
 509                         } 
 510                         // 如果matches数组有内容,则将新对象推入handlerQueue队列中 
 511                         // elem保存着当前元素,handlers这保存着当前元素匹配的handlers 
 512                         if (matches.length) { 
 513                             handlerQueue.push({ 
 514                                 elem: cur, 
 515                                 handlers: matches 
 516                             }); 
 517                         } 
 518                     } 
 519                 } 
 520             } 
 521  
 522             // 如果handlers还有剩余,把剩余的部分也推入到队列中 
 523             if (delegateCount < handlers.length) { 
 524                 handlerQueue.push({ 
 525                     elem: this, 
 526                     handlers: handlers.slice(delegateCount) 
 527                 }); 
 528             } 
 529  
 530             return handlerQueue; 
 531         }, 
 532         // 创建一个jq event对象,让其拥有和原始event一样的属性和值 
 533         fix: function(event) { 
 534             if (event[jQuery.expando]) { 
 535                 return event; 
 536             } 
 537  
 538             var i, prop, copy, 
 539                 type = event.type, 
 540                 originalEvent = event, 
 541                 fixHook = this.fixHooks[type]; 
 542  
 543             // 如果fixHook不存在判断是鼠标事件还是键盘事件再指向相应的钩子对象 
 544             if (!fixHook) { 
 545                 this.fixHooks[type] = fixHook = 
 546                     rmouseEvent.test(type) ? this.mouseHooks : 
 547                     rkeyEvent.test(type) ? this.keyHooks : {}; 
 548             } 
 549             // fixHook是否有props属性,该值是一个数组,如果有则添加到jQuery.event.props中 
 550             copy = fixHook.props ? this.props.concat(fixHook.props) : this.props; 
 551             // 创建一个jQuery Event实例event,默认行为和冒泡fix 
 552             event = new jQuery.Event(originalEvent); 
 553  
 554             // 给jq event添加原始event对象的属性 
 555             i = copy.length; 
 556             while (i--) { 
 557                 prop = copy[i]; 
 558                 event[prop] = originalEvent[prop]; 
 559             } 
 560  
 561             // Support: IE<9 
 562             if (!event.target) { 
 563                 event.target = originalEvent.srcElement || document; 
 564             } 
 565  
 566             // Support: Chrome 23+, Safari? 
 567             if (event.target.nodeType === 3) { 
 568                 event.target = event.target.parentNode; 
 569             } 
 570  
 571             // Support: IE<9 
 572             event.metaKey = !! event.metaKey; 
 573  
 574             // 如果钩子对象有filter解决兼容方法,则返回filter后的event 
 575             return fixHook.filter ? fixHook.filter(event, originalEvent) : event; 
 576         }, 
 577         // event对象相关属性 
 578         props: 'altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which'.split(' '), 
 579         // 后续要用的 
 580         fixHooks: {}, 
 581         // keyEvent钩子 
 582         keyHooks: { 
 583             props: 'char charCode key keyCode'.split(' '), 
 584             filter: function(event, original) { 
 585                 if (event.which == null) { 
 586                     event.which = original.charCode != null ? original.charCode : original.keyCode; 
 587                 } 
 588  
 589                 return event; 
 590             } 
 591         }, 
 592         /* 
 593          mouseEvent钩子,处理有关鼠标事件的兼容性. 
 594          original为原始event对象,event则为jQuery的event对象 
 595          */ 
 596         mouseHooks: { 
 597             props: 'button buttons clientX clientY fromElement offsetX offsetY pageX pageY scrennX screenY toElement'.split(' '), 
 598             filter: function(event, original) { 
 599                 var body, eventDoc, doc, 
 600                     button = original.button, 
 601                     fromElement = original.fromElement; 
 602  
 603                 if (event.pageX == null && original.clientX != null) { 
 604                     eventDoc = event.target.ownerDocument || document; 
 605                     doc = eventDoc.documentElement; 
 606                     body = eventDoc.body; 
 607  
 608                     event.pageX = original.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); 
 609                     event.pageY = original.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); 
 610                 } 
 611  
 612                 if (!event.relatedTarget && fromElement) { 
 613                     event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; 
 614                 } 
 615  
 616                 // 为点击事件添加which属性, 1 === left;2 === middle; 3 === right 
 617                 // 这里没使用button作为属性 
 618                 if (!event.which && button !== undefined) { 
 619                     event.which = (button & 1 ? 1 : (button & 2 ? 3 : (button & 4 ? 2 : 0))); 
 620                 } 
 621  
 622                 return event; 
 623             } 
 624         }, 
 625         /* 
 626          用来处理各事件里的特殊例子 
 627          */ 
 628         special: { 
 629             load: { 
 630                 // 阻止image的load事件冒泡到window.load 
 631                 noBubble: true 
 632             }, 
 633             click: { 
 634                 // For checkbox, fire native event so checked state will be right 
 635                 trigger: function() { 
 636                     if (jQuery.nodeName(this, 'input') && this.type === 'checkbox' && this.click) { 
 637                         this.click(); 
 638                         return false; 
 639                     } 
 640                 } 
 641             }, 
 642             focus: { 
 643                 trigger: function() { 
 644                     if (this !== document.activeElement && this.focus) { 
 645                         try { 
 646                             this.focus(); 
 647                             return false; 
 648                         } catch (e) {} 
 649                     } 
 650                 }, 
 651                 delegateType: 'focusin' 
 652             }, 
 653             blur: { 
 654                 trigger: function() { 
 655                     if (this === document.activeElement && this.blur) { 
 656                         this.blur(); 
 657                         return false; 
 658                     } 
 659                 }, 
 660                 delegateType: 'focusout' 
 661             }, 
 662             beforeunload: { 
 663                 postDispatch: function(event) { 
 664                     // Even when returnValue equals to undefined Firefox will still show alert 
 665                     if (event.result !== undefined) { 
 666                         event.originalEvent.returnValue = event.result; 
 667                     } 
 668                 } 
 669             } 
 670         }, 
 671         // 模拟一个event 
 672         simulate: function(type, elem, event, bubble) { 
 673             var e = jQuery.extend(new jQuery.Event(), 
 674                 event, { 
 675                     type: type, 
 676                     isSimulated: true, 
 677                     originalEvent: {} 
 678                 }); 
 679             if (bubble) { 
 680                 jQuery.event.trigger(e, null, elem); 
 681             } else { 
 682                 jQuery.event.dispatch.call(elem, e); 
 683             } 
 684             if (e.isDefaultPrevented()) { 
 685                 event.preventDefault(); 
 686             } 
 687         } 
 688     }; 
 689  
 690     // 跨浏览器删除事件 
 691     jQuery.removeEvent = document.removeEventListener ? 
 692         function(elem, type, handle) { 
 693             if (elem.removeEventListener) { 
 694                 elem.removeEventListener(type, handle, false); 
 695             } 
 696     } : 
 697         function(elem, type, handle) { 
 698             var name = 'on' + type; 
 699  
 700             if (elem.detachEvent) { 
 701                 if (typeof elem[name] === core_strundefined) { 
 702                     elem[name] = null; 
 703                 } 
 704  
 705                 elem.detachEvent(name, handle); 
 706             } 
 707     }; 
 708  
 709     /* 
 710      Event类用来解决阻止默认行为和事件冒泡兼容的类,src为原始event对象,props则是event的一些预配置项 
 711      */ 
 712     jQuery.Event = function(src, props) { 
 713         if (!(this instanceof jQuery.Event)) { 
 714             return new jQuery.Event(src, props); 
 715         } 
 716  
 717         if (src && src.type) { 
 718             this.originalEvent = src; 
 719             this.type = src.type; 
 720  
 721             this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; 
 722         } else { 
 723             this.type = src; 
 724         } 
 725  
 726         if (props) { 
 727             jQuery.extend(this, props); 
 728         } 
 729  
 730         this.timeStamp = src && src.timeStamp || jQuery.now(); 
 731  
 732         this[jQuery.expando] = true; 
 733     }; 
 734  
 735     jQuery.Event.prototype = { 
 736         isDefaultPrevented: returnFalse, 
 737         isPropagationStopped: returnFalse, 
 738         isImmediatePropagationStopped: returnFalse, 
 739  
 740         preventDefault: function() { 
 741             var e = this.originalEvent; 
 742  
 743             this.isDefaultPrevented = returnTrue; 
 744             if (!e) { 
 745                 return; 
 746             } 
 747  
 748             if (e.preventDefault) { 
 749                 e.preventDefault(); 
 750             } else { 
 751                 e.returnValue = false; 
 752             } 
 753         }, 
 754         stopPropagation: function() { 
 755             var e = this.originalEvent; 
 756  
 757             this.isPropagationStopped = returnTrue; 
 758             if (!e) { 
 759                 return; 
 760             } 
 761             if (e.stopPropagation) { 
 762                 e.stopPropagation(); 
 763             } 
 764             e.cancelBubble = true; 
 765         }, 
 766         stopImmediatePropagation: function() { 
 767             this.isImmediatePropagationStopped = returnTrue; 
 768             this.stopPropagation(); 
 769         } 
 770     }; 
 771  
 772     jQuery.each({ 
 773         mouseenter: 'mouseover', 
 774         mouseleave: 'mouseout' 
 775     }, function(orig, fix) { 
 776         jQuery.event.special[orig] = { 
 777             delegateType: fix, 
 778             bindType: fix, 
 779  
 780             handle: function(event) { 
 781                 var ret, 
 782                     target = this, 
 783                     related = event.relatedTarget, 
 784                     handleObj = event.handleObj; 
 785  
 786                 // For mousenter/leave call the handler if related is outside the target. 
 787                 // NB: No relatedTarget if the mouse left/entered the browser window 
 788                 // 确保相关元素是在目标元素的外面, 
 789                 // 没有相关元素指的是移到/移出浏览器外 
 790                 if (!related || (related !== target && !jQuery.contains(target, related))) { 
 791                     event.type = handleObj.origType; 
 792                     ret = handleObj.handler.apply(this, arguments); 
 793                     event.type = fix; 
 794                 } 
 795                 return ret; 
 796             } 
 797         }; 
 798     }); 
 799  
 800     // IE submit 委托 
 801     if (!jQuery.support.submitBubbles) { 
 802         jQuery.event.special.submit = { 
 803             setup: function() { 
 804                 if (jQuery.nodeName(this, 'form')) { 
 805                     return false; 
 806                 } 
 807  
 808                 // Lazy-add a submit handler when a descendant form may potentially be submitted 
 809                 jQuery.event.add(this, 'click._submit keypress._submit', function(e) { 
 810                     // Node name check avoids a VML-related crash in IE (#9807) 
 811                     var elem = e.target, 
 812                         form = jQuery.nodeName(elem, 'input') || jQuery.nodeName(elem, 'button') ? elem.form : undefined; 
 813                     if (form && !jQuery._data(form, 'submitBubbles')) { 
 814                         jQuery.event.add(form, 'submit._submit', function(event) { 
 815                             event._submit_bubble = true; 
 816                         }); 
 817                         jQuery._data(form, 'submitBubbles', true); 
 818                     } 
 819                 }); 
 820                 // return undefined since we don't need an event listener 
 821             }, 
 822             postDispatch: function(event) { 
 823                 // If form was submitted by the user, bubble the event up the tree 
 824                 if (event._submit_bubble) { 
 825                     delete event._submit_bubble; 
 826                     if (this.parentNode && !event.isTrigger) { 
 827                         jQuery.event.simulate('submit', this.parentNode, event, true); 
 828                     } 
 829                 } 
 830             }, 
 831             teardown: function() { 
 832                 // Only need this for delegated form submit events 
 833                 if (jQuery.nodeName(this, 'form')) { 
 834                     return false; 
 835                 } 
 836  
 837                 // Remove delegated handlers; cleanData eventually reaps submit handlers attached above 
 838                 jQuery.event.remove(this, '._submit'); 
 839             } 
 840         }; 
 841     } 
 842  
 843     // IE change delegation and checkbox/radio fix 
 844     if (!jQuery.support.changeBubbles) { 
 845  
 846         jQuery.event.special.change = { 
 847  
 848             setup: function() { 
 849  
 850                 if (rformElems.test(this.nodeName)) { 
 851                     // IE doesn't fire change on a check/radio until blur; trigger it on click 
 852                     // after a propertychange. Eat the blur-change in special.change.handle. 
 853                     // This still fires onchange a second time for check/radio after blur. 
 854                     if (this.type === "checkbox" || this.type === "radio") { 
 855                         jQuery.event.add(this, "propertychange._change", function(event) { 
 856                             if (event.originalEvent.propertyName === "checked") { 
 857                                 this._just_changed = true; 
 858                             } 
 859                         }); 
 860                         jQuery.event.add(this, "click._change", function(event) { 
 861                             if (this._just_changed && !event.isTrigger) { 
 862                                 this._just_changed = false; 
 863                             } 
 864                             // Allow triggered, simulated change events (#11500) 
 865                             jQuery.event.simulate("change", this, event, true); 
 866                         }); 
 867                     } 
 868                     return false; 
 869                 } 
 870                 // Delegated event; lazy-add a change handler on descendant inputs 
 871                 jQuery.event.add(this, "beforeactivate._change", function(e) { 
 872                     var elem = e.target; 
 873  
 874                     if (rformElems.test(elem.nodeName) && !jQuery._data(elem, "changeBubbles")) { 
 875                         jQuery.event.add(elem, "change._change", function(event) { 
 876                             if (this.parentNode && !event.isSimulated && !event.isTrigger) { 
 877                                 jQuery.event.simulate("change", this.parentNode, event, true); 
 878                             } 
 879                         }); 
 880                         jQuery._data(elem, "changeBubbles", true); 
 881                     } 
 882                 }); 
 883             }, 
 884  
 885             handle: function(event) { 
 886                 var elem = event.target; 
 887  
 888                 // Swallow native change events from checkbox/radio, we already triggered them above 
 889                 if (this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox")) { 
 890                     return event.handleObj.handler.apply(this, arguments); 
 891                 } 
 892             }, 
 893  
 894             teardown: function() { 
 895                 jQuery.event.remove(this, "._change"); 
 896  
 897                 return !rformElems.test(this.nodeName); 
 898             } 
 899         }; 
 900     } 
 901  
 902     // Create "bubbling" focus and blur events 
 903     if (!jQuery.support.focusinBubbles) { 
 904         jQuery.each({ 
 905             focus: "focusin", 
 906             blur: "focusout" 
 907         }, function(orig, fix) { 
 908  
 909             // Attach a single capturing handler while someone wants focusin/focusout 
 910             var attaches = 0, 
 911                 handler = function(event) { 
 912                     jQuery.event.simulate(fix, event.target, jQuery.event.fix(event), true); 
 913                 }; 
 914  
 915             jQuery.event.special[fix] = { 
 916                 setup: function() { 
 917                     if (attaches++ === 0) { 
 918                         document.addEventListener(orig, handler, true); 
 919                     } 
 920                 }, 
 921                 teardown: function() { 
 922                     if (--attaches === 0) { 
 923                         document.removeEventListener(orig, handler, true); 
 924                     } 
 925                 } 
 926             }; 
 927         }); 
 928     } 
 929  
 930     jQuery.fn.extend({ 
 931         on: function(types, selector, data, fn, /*INTERNAL*/ one) { 
 932             var type, origFn; 
 933  
 934             // 添加多个事件注册 
 935             if (typeof types === "object") { 
 936                 // ( types-Object, selector, data ) 
 937                 if (typeof selector !== "string") { 
 938                     // ( types-Object, data ) 
 939                     data = data || selector; 
 940                     selector = undefined; 
 941                 } 
 942                 // 为每个事件迭代 
 943                 for (type in types) { 
 944                     this.on(type, selector, data, types[type], one); 
 945                 } 
 946                 return this; 
 947             } 
 948  
 949             // 如果data和fn都为空,则将selector赋值给fn, 
 950             if (data == null && fn == null) { 
 951                 // ( types, fn ) 
 952                 fn = selector; 
 953                 data = selector = undefined; 
 954             } else if (fn == null) { 
 955                 if (typeof selector === "string") { 
 956                     // ( types, selector, fn ) 
 957                     fn = data; 
 958                     data = undefined; 
 959                 } else { 
 960                     // ( types, data, fn ) 
 961                     fn = data; 
 962                     data = selector; 
 963                     selector = undefined; 
 964                 } 
 965             } 
 966             if (fn === false) { 
 967                 fn = returnFalse; 
 968             } else if (!fn) { 
 969                 return this; 
 970             } 
 971  
 972             // 如果只是一次性事件,则将fn从新包装 
 973             if (one === 1) { 
 974                 origFn = fn; 
 975                 fn = function(event) { 
 976                     // 这里使用空的jq对象来解除事件绑定信息, 
 977                     // 具体定位是通过event.handleObj和目标元素event.delegateTarget 
 978                     jQuery().off(event); 
 979                     // 执行原始的fn函数 
 980                     return origFn.apply(this, arguments); 
 981                 }; 
 982                 // Use same guid so caller can remove using origFn 
 983                 // 备忘信息 
 984                 fn.guid = origFn.guid || (origFn.guid = jQuery.guid++); 
 985             } 
 986             // 统一调用jQuery.event.add方法添加事件处理 
 987             return this.each(function() { 
 988                 jQuery.event.add(this, types, fn, data, selector); 
 989             }); 
 990         }, 
 991         one: function(types, selector, data, fn) { 
 992             return this.on(types, selector, data, fn, 1); 
 993         }, 
 994         off: function(types, selector, fn) { 
 995             var handleObj, type; 
 996             // 当传递的types是jQuery创建的event对象时 
 997             if (types && types.preventDefault && types.handleObj) { 
 998                 // ( event )  dispatched jQuery.Event 
 999                 handleObj = types.handleObj; 
1000                 jQuery(types.delegateTarget).off( 
1001                     handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, 
1002                     handleObj.selector, 
1003                     handleObj.handler 
1004                 ); 
1005                 return this; 
1006             } 
1007             // 当types是对象,遍历递归 
1008             if (typeof types === "object") { 
1009                 // ( types-object [, selector] ) 
1010                 for (type in types) { 
1011                     this.off(type, selector, types[type]); 
1012                 } 
1013                 return this; 
1014             } 
1015             if (selector === false || typeof selector === "function") { 
1016                 // ( types [, fn] ) 
1017                 fn = selector; 
1018                 selector = undefined; 
1019             } 
1020             if (fn === false) { 
1021                 fn = returnFalse; 
1022             } 
1023             // 统一调用jQuery.event.remove移除事件处理程序及相关信息 
1024             return this.each(function() { 
1025                 jQuery.event.remove(this, types, fn, selector); 
1026             }); 
1027         }, 
1028         bind: function(types, data, fn) { 
1029             return this.on(types, null, data, fn); 
1030         }, 
1031         unbind: function(types, fn) { 
1032             return this.off(types, null, fn); 
1033         }, 
1034         delegate: function(selector, types, data, fn) { 
1035             return this.on(types, selector, data, fn); 
1036         }, 
1037         undelegate: function(selector, types, fn) { 
1038             // ( namespace ) or ( selector, types [, fn] ) 
1039             return arguments.length === 1 ? this.off(selector, "**") : this.off(types, selector || "**", fn); 
1040         }, 
1041         trigger: function(type, data) { 
1042             return this.each(function() { 
1043                 jQuery.event.trigger(type, data, this); 
1044             }); 
1045         }, 
1046         triggerHandler: function(type, data) { 
1047             var elem = this[0]; 
1048             if (elem) { 
1049                 return jQuery.event.trigger(type, data, elem, true); 
1050             } 
1051         } 
1052     });

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