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 });