jQuery源码阅读13-4:事件委托delegate流程

三天前BAE上mongo的collections都被他们系统删了, 三天了也没给个回复, 这会儿突然能用了 不知道是不是回光返照…
这篇文章早写好了也发不上来 以后全部数据我都写程序定期导出, BAE的运维太吓人了 慎用BAE啊…

书归正传: 和原生的事件委托类似, 也是在父级dom上绑定事件处理函数, 利用事件冒泡, 父级接收事件并判断是不是该相应委托.
jQ的事件实际上是存在缓存里的, 父级dom接收到事件后, 会从缓存里拿到包装好的事件队列, 然后靠jQ.Event实例处理事件

委托示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 博文作者:ruantao1989{@}gmail.com
* 引自博客:ruantao.duapp.com/blog
*/
//最简单实例: "接收委托的元素".delegate("被委托元素","事件类型",data{},fn)
//d3是最内层, 外层是d2, 最外层是d1. 事件冒泡顺序d3==> d2==> d1
//为指定元素d2(属于被选元素d1),添加事件委托
$(".d1").delegate(".d2,.d3", "click", function() {
//$(this).css('background-color', 'black');
alert("id==>"+this.className);
});
function undelegate_click(){
$(".d1").undelegate("click");
}

零:

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
jQuery.fn.extend({
//live和die在1.9+的版本里被废除了
live: function( types, data, fn ) {
//this.context默认是document, 也就是默认是把所有live()委托到document上
//这个我觉得是废除live的主要原因: 要是太多个元素都委托在document上, 性能反倒会有问题
jQuery( this.context ).on( types, this.selector, data, fn );
return this;
},
die: function( types, fn ) {
jQuery( this.context ).off( types, this.selector || "**", fn );
return this;
},
//==>源码3588行
delegate: function( selector, types, data, fn ) {
//相当于on("事件类型","被委托选择器",data{},fn)
return this.on( types, selector, data, fn );
},
undelegate: function( selector, types, fn ) {
return arguments.length === 1 ?
this.off( selector, "**" ) //传入: "命名空间"
: this.off( types, selector || "**", fn );//( selector, types [, fn] )
},
//相比bind, 传给on()的第二个参数(也就是selector) 不为null时, 是启动委托模式
/*bind: function( types, data, fn ) {
return this.on( types, null, data, fn );
},
unbind: function( types, fn ) {
return this.off( types, null, fn );
},*/

一:

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
//第一步
jQuery.fn.extend({
on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
var origFn, type;
//types是对象, 和委托没什么关系
if ( typeof types === "object" ) {
...
}
//一般情况下 data是事件处理函数, fn一般是undefined
if ( data == null && fn == null ) {
...
} else if ( fn == null ) {
//这里是常用的情况:
//选择器是字串
if ( typeof selector === "string" ) {
//传参格式为( "事件类型", "选择器字串", "事件处理函数" )
fn = data;
data = undefined;//把传参还原, 处理函数本该在fn上, data由于没有指定需要清空
} else {
...
}
}
if ( fn === false ) {
//处理函数是false就返回false
fn = returnFalse;
} else if ( !fn ) {
//到这还没有处理函数, 就说不过去了, 直接不予处理
return this;
}
//one()的情况
if ( one === 1 ) {
...
}
//最终遍历add()
return this.each( function() {
//传参( 被委托dom, "事件类型", 处理函数, data对象, "选择器" )
jQuery.event.add( this, types, fn, data, selector );
});
},

二:

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
//==>源码2642行
//第二步
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;
}
//传入已被jQ.event对象处理过的handler
if ( handler.handler ) {
...
}
//没有guid就分配
if ( !handler.guid ) {
handler.guid = jQuery.guid++;
}
//下边这部分对elemData的处理,实际上就是对cache中此对象缓存的处理
//elemData.events是缓存的事件数组,之前有可能有已缓存的事件
events = elemData.events;
if ( !events ) {
elemData.events = events = {};
}
eventHandle = elemData.handle;
if ( !eventHandle ) {
elemData.handle = eventHandle = function( e ) {
//绑定事件的处理函数实际上使用了jQuery.event.dispatch
return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
undefined;
};
eventHandle.elem = elem;
}
// Handle multiple events separated by a space
// jQuery(...).bind("mouseover mouseout", fn);
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 ) {
...
}
//不论是delegate()还是on(), selector参数有值的, 就是有委托,
if ( selector ) {
//通过delegateCount来控制插入的位置
handlers.splice( handlers.delegateCount++, 0, handleObj );
} else {
handlers.push( handleObj );
}
//global里的标记,记录某个事件是不是存在
jQuery.event.global[ type ] = true;
}
// Nullify elem to prevent memory leaks in IE
elem = null;
},

三:

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
/**
* 博文作者: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;
//事件的委托是当前dom, 因为子元素冒泡上来, 设置过委托的dom会接受此事件
event.delegateTarget = this;
//特殊处理里如果有preDispatch钩子, 就执行, 返回false就有问题, 直接return.
//这个钩子应该是预留的 我在源码里没找到
if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
return;
}
//有事件委托, 且事件是点击事件
if ( delegateCount && !(event.button && event.type === "click") ) {
//cur是事件源, 如果事件源不是当前dom, 则向上追溯, 直到cur就是当前接收事件的dom
for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
//未被disable, 或不是"cilck"(后一个判断是个兼容处理)
if ( cur.disabled !== true || event.type !== "click" ) {
selMatch = {};
matches = [];
for ( i = 0; i < delegateCount; i++ ) {
//取出缓存中的一个事件处理函数
handleObj = handlers[ i ];
//绑定时的选择器
sel = handleObj.selector;
//尝试去selMatch数组里取
if ( selMatch[ sel ] === undefined ) {
//通过选择器拿到对应结果的个数, 目的是检验是否有对应的dom存在
selMatch[ sel ] = handleObj.needsContext ?
jQuery( sel, this ).index( cur ) >= 0 :
jQuery.find( sel, this, null, [ cur ] ).length;
}
//把匹配项放入matches
if ( selMatch[ sel ] ) {
matches.push( handleObj );
}
}
//如果matches有值,就放入handlerQueue待执行
if ( matches.length ) {
//遍历matches对象, 组装{elem:, matches:}对象
//这样就把委托对象, 和受委托的多个事件插入handlerQueue了
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;
},