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