jQuery源码阅读13-1:事件绑定流程

Event这个摸块大概用了1千行, 占源代码的1/9, 我知道代码量挺大 但是没想到就看一个绑定过程(边跟边记)就得花五六个小时…挺好的周末夜晚就这样到凌晨1点了…
这篇按时间顺序 说说jQ事件绑定还有event对象fix的流程

事件这块结构大概是这样:
1.外层: fn里有on,bind,live这些常用的接口
2.中间层: 上述接口会调用jQuery.event对象中的基础方法, 如add(). jQuery.event对象只是真正的执行者, 主要负责做(1)data缓存, (2)做兼容, (3)最终生成jQuery.Event实例 (4)dispatch执行
3.内层: jQuery.Event实例包装原生事件对象, 记录扩展的data, 记录执行函数结果等

绑定事件的核心就是用elem.addEventListener或elem.attachEvent绑定这个执行函数:

1
jQuery.event.dispatch.apply( eventHandle.elem, arguments )

触发时, 通过elem从cache中取出原生事件对象 并加工成jQ.Event实例, 放入执行队列, 通过此实例扩展data或带回执行结果.

零: 绑定的几种形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 博文作者:ruantao1989{@}gmail.com
* 引自博客:ruantao.duapp.com/blog
*/
//1普通用法,给选择出来的dom添加事件
$("p").on("click", function(){
alert( $(this).text() );
});
//2添加事件的同时,可以传入一个参数
function myHandler(event) {
alert(event.data.foo);
}
$("p").on("click", {foo: "bar"}, myHandler)
//3第二个参数传false,阻止默认事件
$("p").on("click", false)

一: 对外接口 fn.on

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//==>源码3485行
jQuery.fn.extend({
//第一步
on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
var origFn, type;
//事件类型可以传入一个types或handlers对象
if ( typeof types === "object" ) {
// ( types-Object, selector, data )
if ( typeof selector !== "string" ) { // && selector != null
// ( types-Object, data )
data = data || selector;
selector = undefined;
}
//遍历事件类型对象, 逐个on上
for ( type in types ) {
this.on( type, selector, data, types[ type ], one );
}
return this;
}
if ( data == null && fn == null ) {
//第(1)种用法, 如.on("click", function(){alert( $(this).text() );});
fn = selector;//fn赋值为第二个参数(事件处理函数)
data = selector = undefined;//data,selector,one这仨都是undefined
} else if ( fn == null ) {
if ( typeof selector === "string" ) {
// ( types, selector, fn )
fn = data;
data = undefined;
} else {
// ( types, data, fn )
fn = data;
data = selector;
selector = undefined;
}
}
//第(3)种用法, fn就是return false
if ( fn === false ) {
fn = returnFalse;
} else if ( !fn ) {
return this;
}
//fn.one时候为1,触发一次就off掉
if ( one === 1 ) {
origFn = fn;
fn = function( event ) {
// Can use an empty set, since event contains the info
jQuery().off( event );
return origFn.apply( this, arguments );
};
// Use same guid so caller can remove using origFn
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
}
//处理好各个参数之后, 遍历当前对象, 都执行jQuery.event.add事件添加
return this.each( function() {
jQuery.event.add( this, types, fn, data, selector );
});
},

二: event.add实现绑定 (其实初始化时候, event做了很多兼容性的预处理, 在这个时间点之前, 这些这个本文最后贴出来)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
* 博文作者:ruantao1989{@}gmail.com
* 引自博客:ruantao.duapp.com/blog
*/
//==>源码2642行
jQuery.event = {
//第二步
add: function( elem, types, handler, data, selector ) {
var elemData, eventHandle, events,
t, tns, type, namespaces, handleObj,
handleObjIn, handlers, special;
//nodeType不对,没指定事件类型,没有处理函数,缓存不上的,都直接返回
//其中elemData是调用jQuery.data生成的缓存, 如果是在dom元素上缓存, 会生成一个id关联jQuery.cache中的对应id的对象(具体过程jQ阅读-11详细分析过)
//此处elemData是jQuery.cache[对应id]的对象
if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
return;
}
//传入的处理函数,如果有.handler属性
if ( handler.handler ) {
handleObjIn = handler;
handler = handleObjIn.handler;
selector = handleObjIn.selector;
}
//处理函数handler如果没有guid就给分配一个
//handler是function, 在实例上直接加guid属性
if ( !handler.guid ) {
handler.guid = jQuery.guid++;
}
//下边这部分对elemData的处理,实际上就是对cache中此对象缓存的处理
//elemData.events是缓存的事件数组,之前有可能有已缓存的事件
events = elemData.events;
if ( !events ) {
//之前没有缓存对象, 创建一个空对象, 刚刚undefined的events也置成空的
elemData.events = events = {};
}
//elemData.handle是缓存对象里保存dom元素信息的, 触发时使用
eventHandle = elemData.handle;
if ( !eventHandle ) {
//要是之前没有, 就添加一个
//此处是核心
elemData.handle = eventHandle = function( e ) {
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
//绑定事件的处理函数实际上使用了jQuery.event.dispatch
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
undefined;
};
//官方注释:添加元素进来,是为了防止低内存时候的IE non-native events
eventHandle.elem = elem;
}
//传来的多个事件类型(字串,如("mouseover mouseout",function())), 在这转成数组
types = jQuery.trim( hoverHack(types) ).split( " " );
for ( t = 0; t < types.length; t++ ) {
//正则筛选出事件的命名空间和事件, 如 click.ns ,过正则就是["clcik.ns", "clcik", "ns"]
tns = rtypenamespace.exec( types[t] ) || [];
type = tns[1];
namespaces = ( tns[2] || "" ).split( "." ).sort();
//是否需要special处理, 类似之前的钩子
//special对象包括如下属性:
//{beforeunload: Object,,blur: Object,focus: Object,focusin: Object,focusout: Object,load: Object,mouseenter: Object,mouseleave: Object}
special = jQuery.event.special[ type ] || {};
//其中一个对象的结构如下:
//mouseenter: Object
// -bindType: "mouseover"
// -delegateType: "mouseover"
// -handle: function ( event ) {
// -__proto__: Object
// 如果传来selector,就优先检查special.delegateType,
//没传来就用special.bindType, 都没有就还用原事件类型type
type = ( selector ? special.delegateType : special.bindType ) || type;
//根据修正的delegateType或bindType,再查一次是否需要特殊处理
special = jQuery.event.special[ type ] || {};
//把所有和事件相关的参数,都组装进handleObjIn对象
handleObj = jQuery.extend({
type: type,//事件类型
origType: tns[1],//未修正之前的事件类型
data: data,//第(2)种用法, 传来的参数对象
handler: handler,//事件处理函数
guid: handler.guid,//唯一id
selector: selector,
needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
namespace: namespaces.join(".")//命名空间转回字串
}, handleObjIn );
//尝试取出事件对应的处理函数
handlers = events[ type ];
if ( !handlers ) {
//如果没取到, 就是要写入一个新的handlers
handlers = events[ type ] = [];
handlers.delegateCount = 0;
//非special的情况,直接
if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
//绑定事件
//这的eventHandle是jQuery.event.dispatch.apply( eventHandle.elem, arguments ),通过dispatch分发的
if ( elem.addEventListener ) {
elem.addEventListener( type, eventHandle, false );
//做兼容处理
} else if ( elem.attachEvent ) {
elem.attachEvent( "on" + type, eventHandle );
}
}
}
if ( special.add ) {
special.add.call( elem, handleObj );
if ( !handleObj.handler.guid ) {
handleObj.handler.guid = handler.guid;
}
}
// Add to the element's handler list, delegates in front
if ( selector ) {
handlers.splice( handlers.delegateCount++, 0, handleObj );
} else {
handlers.push( handleObj );
}
//global初始化为{}, 在这对该type做个标记, 触发事件时候判断用
jQuery.event.global[ type ] = true;
}
//防止IE不能释环引用过来的对象
elem = null;
},

三: event.dispatch事件触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/**
* 博文作者:ruantao1989{@}gmail.com
* 引自博客:ruantao.duapp.com/blog
*/
//==>源码2985行
//第三步
dispatch: function( event ) {
//经过fix修复过的jQ.Event实例, 里边属性都修复过了, 也包装了原生事件对象
event = jQuery.event.fix( event || window.event );
var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,
//取出在add时放入cache的缓存对象,的events属性, 的事件属性数组, 没有就是[]
//jQuery.cache
// --Object
// ----1: Object
// ------events: Object
// ------click: Array[1]
handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
//拿到委托数目
delegateCount = handlers.delegateCount,
//转成真实数组
args = core_slice.call( arguments ),
run_all = !event.exclusive && !event.namespace,
//是否需要特殊处理
special = jQuery.event.special[ event.type ] || {},
//执行队列
handlerQueue = [];
//jQuery.event.fix过的event对象, 原生事件对象只读
args[0] = event;
event.delegateTarget = this;
//特殊处理里如果有preDispatch钩子, 就执行, 返回false就有问题, 直接return.
//这个钩子应该是预留的 我在源码里没找到
if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
return;
}
//有事件委托的话, 这个等看委托时候再分析
if ( delegateCount && !(event.button && event.type === "click") ) {
for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
// Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764)
if ( cur.disabled !== true || event.type !== "click" ) {
selMatch = {};
matches = [];
for ( i = 0; i < delegateCount; i++ ) {
handleObj = handlers[ i ];
sel = handleObj.selector;
if ( selMatch[ sel ] === undefined ) {
selMatch[ sel ] = handleObj.needsContext ?
jQuery( sel, this ).index( cur ) >= 0 :
jQuery.find( sel, this, null, [ cur ] ).length;
}
if ( selMatch[ sel ] ) {
matches.push( handleObj );
}
}
if ( matches.length ) {
handlerQueue.push({ elem: cur, matches: matches });
}
}
}
}
//没有事件委托的话
if ( handlers.length > delegateCount ) {
//执行队列里push进对象 {elem:dom对象, matches:执行数组副本}
handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
}
//遍历执行队列(event没有停止传播) 终于到执行了...
for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
//取出队列中的一项
matched = handlerQueue[ i ];
event.currentTarget = matched.elem;
for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
//具体执行函数, 在cache上的引用
handleObj = matched.matches[ j ];
// Triggered event must either 1) be non-exclusive and have no namespace, or
// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
//第(2)种用法, 指定data
event.data = handleObj.data;
//在实践对象里增加一个handleObj的引用
event.handleObj = handleObj;
//核心
//(jQuery.event.special[ handleObj.origType ] || {}).handle是执行看特殊处理原始事件类型.handle
//没有就handleObj.handler.apply( matched.elem这个Dom对象, args );
//handleObj.handler是最开始传入的alert(), args是相应的jQ.Event实例
ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
.apply( matched.elem, args );
//事件处理函数中有返回值,需记录
if ( ret !== undefined ) {
event.result = ret;
//如果在事件处理函数中返回false就阻止默认事件, 阻止传播
if ( ret === false ) {
event.preventDefault();
event.stopPropagation();
}
}
}
}
}
//如果有特殊的后处理, 在执行
if ( special.postDispatch ) {
special.postDispatch.call( this, event );
}
//返回: 事件处理函数中有返回值
return event.result;
},

四: jQ.Event对象和对原生对象的修复 ( 这个出现时间点其实在第三步的中间 这样写比较清楚 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
* 博文作者:ruantao1989{@}gmail.com
* 引自博客:ruantao.duapp.com/blog
*/
//==>源码3241行
//jQ自定义event的构造函数
jQuery.Event = function( src, props ) {
//无new的工厂
if ( !(this instanceof jQuery.Event) ) {
//一来能省new, 二来能省return
return new jQuery.Event( src, props );
}
//往实例里写参数
if ( src && src.type ) {
//原生事件对象包装在originalEvent属性里
this.originalEvent = src;
//单独抽出原生对象的类型
this.type = src.type;
// Events bubbling up the document may have been marked as prevented
// by a handler lower down the tree; reflect the correct value.
//是否阻止默认事件标志, 做兼容判断, 留个回调返回 true/false
this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
// Event type
} else {
this.type = src;
}
//如果传入props数组则合并
if ( props ) {
jQuery.extend( this, props );
}
//原生事件对象中没有时间戳的, 填一个时间戳
this.timeStamp = src && src.timeStamp || jQuery.now();
//实例中添加属性jQuery.expando唯一id : true
this[ jQuery.expando ] = true;
};
//==>源码3130行
fix: function( event ) {
//检查event对象中,存不存在唯一id:jQuery.expando
if ( event[ jQuery.expando ] ) {
return event;
}
// Create a writable copy of the event object and normalize some properties
var i, prop,
originalEvent = event,//原始事件对象
fixHook = jQuery.event.fixHooks[ event.type ] || {},//检查是否需要hook
//如果有钩子,则使用钩子的props+jQuery.event.props, 否者使用jQuery.event.props
copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
//通过jQuery.Event这个工厂, 生成一个实例
event = jQuery.Event( originalEvent );
//遍历copy
for ( i = copy.length; i; ) {
prop = copy[ --i ];
//把原生event对象中的需要处理的属性, 逐个拷贝进jQ的event实例(比如screenX,screenY这种杂七杂八的属性 以备后用)
event[ prop ] = originalEvent[ prop ];
}
//处理IE中没有event.target属性
if ( !event.target ) {
event.target = originalEvent.srcElement || document;
}
//处理Safari下的一个bug: #504
if ( event.target.nodeType === 3 ) {
event.target = event.target.parentNode;
}
//如果event.metaKey没有,metaKey应该为false(IE678的mouse/key事件)
event.metaKey = !!event.metaKey;
//如果有fixHook.filter还得调一次fixHook.filter( event, originalEvent ),
//真正的执行钩子, 如做clientX,clientY换算, 左右按键的兼容性等, 执行完会返回一个修正过的event对象
//详见mouseHooks和keyHooks
return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
},

附: special处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//==>源码3639行
//下边这些事件都需要修正,
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
//改写事件处理, 比如jQuery.fn.blur改为 = function(data, fn){...}
jQuery.fn[ name ] = function( data, fn ) {
if ( fn == null ) {
fn = data;
data = null;
}
return arguments.length > 0 ?
this.on( name, null, data, fn ) :
this.trigger( name );
};
//创建fixHooks, 初始化为{}, 以下逐个添加 , jQuery.event.fix中调用
// rkeyEvent = /^key/, 以"key"开头
if ( rkeyEvent.test( name ) ) {
jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
}
//rmouseEvent = /^(?:mouse|contextmenu)|click/, mouse开头或左右键,
if ( rmouseEvent.test( name ) ) {
//如jQuery.event.fixHooks.click = jQuery.event.mouseHooks;
jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
}
});
//==>源码3085行
keyHooks: {
props: "char charCode key keyCode".split(" "),
filter: function( event, original ) {
// Add which for key events
if ( event.which == null ) {
event.which = original.charCode != null ? original.charCode : original.keyCode;
}
return event;
}
},
mouseHooks: {
props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
filter: function( event, original ) {
var eventDoc, doc, body,
button = original.button,
fromElement = original.fromElement;
//根据兼容性计算event.pageX和event.pageY
if ( event.pageX == null && original.clientX != null ) {
eventDoc = event.target.ownerDocument || document;
doc = eventDoc.documentElement;
body = eventDoc.body;
event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
}
//增加event.relatedTarget,不知道干嘛用的
if ( !event.relatedTarget && fromElement ) {
event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
}
//event.which是鼠标按键 1 === 左键; 2 === 中键; 3 === 右键
if ( !event.which && button !== undefined ) {
event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
}
return event;
}
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//==>源码3164行
special: {
load: {
// Prevent triggered image.load events from bubbling to window.load
noBubble: true
},
focus: {
delegateType: "focusin"
},
blur: {
delegateType: "focusout"
},
beforeunload: {
setup: function( data, namespaces, eventHandle ) {
// We only want to do this special case on windows
if ( jQuery.isWindow( this ) ) {
this.onbeforeunload = eventHandle;
}
},
teardown: function( namespaces, eventHandle ) {
if ( this.onbeforeunload === eventHandle ) {
this.onbeforeunload = null;
}
}
}
},
//==>源码3324行
//特殊事件处理
//把mouseenter修正为"mouseover", mouseleave修正为"mouseout"
jQuery.each({
mouseenter: "mouseover",
mouseleave: "mouseout"
}, function( orig, fix ) {
//jQuery.event.special[mouseover] 和 jQuery.event.special[mouseout]
jQuery.event.special[ orig ] = {
delegateType: fix,
bindType: fix,
handle: function( event ) {
var ret,
target = this,
related = event.relatedTarget,
handleObj = event.handleObj,
selector = handleObj.selector;
//mousenter/leave事件,如果没有related, 或target不包含related, 要修改event
if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
event.type = handleObj.origType;
ret = handleObj.handler.apply( this, arguments );
event.type = fix;
}
return ret;
}
};
});
// IE submit delegation
if ( !jQuery.support.submitBubbles ) {
jQuery.event.special.submit = {
setup: function() {
// Only need this for delegated form submit events
if ( jQuery.nodeName( this, "form" ) ) {
return false;
}
// Lazy-add a submit handler when a descendant form may potentially be submitted
jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
// Node name check avoids a VML-related crash in IE (#9807)
var elem = e.target,
form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
if ( form && !jQuery._data( form, "_submit_attached" ) ) {
jQuery.event.add( form, "submit._submit", function( event ) {
event._submit_bubble = true;
});
jQuery._data( form, "_submit_attached", true );
}
});
// return undefined since we don't need an event listener
},
postDispatch: function( event ) {
// If form was submitted by the user, bubble the event up the tree
if ( event._submit_bubble ) {
delete event._submit_bubble;
if ( this.parentNode && !event.isTrigger ) {
jQuery.event.simulate( "submit", this.parentNode, event, true );
}
}
},
teardown: function() {
// Only need this for delegated form submit events
if ( jQuery.nodeName( this, "form" ) ) {
return false;
}
// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
jQuery.event.remove( this, "._submit" );
}
};
}
// IE change delegation and checkbox/radio fix
if ( !jQuery.support.changeBubbles ) {
jQuery.event.special.change = {
setup: function() {
if ( rformElems.test( this.nodeName ) ) {
// IE doesn't fire change on a check/radio until blur; trigger it on click
// after a propertychange. Eat the blur-change in special.change.handle.
// This still fires onchange a second time for check/radio after blur.
if ( this.type === "checkbox" || this.type === "radio" ) {
jQuery.event.add( this, "propertychange._change", function( event ) {
if ( event.originalEvent.propertyName === "checked" ) {
this._just_changed = true;
}
});
jQuery.event.add( this, "click._change", function( event ) {
if ( this._just_changed && !event.isTrigger ) {
this._just_changed = false;
}
// Allow triggered, simulated change events (#11500)
jQuery.event.simulate( "change", this, event, true );
});
}
return false;
}
// Delegated event; lazy-add a change handler on descendant inputs
jQuery.event.add( this, "beforeactivate._change", function( e ) {
var elem = e.target;
if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) {
jQuery.event.add( elem, "change._change", function( event ) {
if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
jQuery.event.simulate( "change", this.parentNode, event, true );
}
});
jQuery._data( elem, "_change_attached", true );
}
});
},
handle: function( event ) {
var elem = event.target;
// Swallow native change events from checkbox/radio, we already triggered them above
if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
return event.handleObj.handler.apply( this, arguments );
}
},
teardown: function() {
jQuery.event.remove( this, "._change" );
return !rformElems.test( this.nodeName );
}
};
}
// Create "bubbling" focus and blur events
if ( !jQuery.support.focusinBubbles ) {
jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
// Attach a single capturing handler while someone wants focusin/focusout
var attaches = 0,
handler = function( event ) {
jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
};
jQuery.event.special[ fix ] = {
setup: function() {
if ( attaches++ === 0 ) {
document.addEventListener( orig, handler, true );
}
},
teardown: function() {
if ( --attaches === 0 ) {
document.removeEventListener( orig, handler, true );
}
}
};
});
}
//==>源码2632行
hoverHack = function( events ) {
return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
};