jQuery源码阅读13-2:事件删除流程

由于事件的绑定实际上是把对应的处理函数添加到cache中, 再addEventListener或attachEvent绑定jQuery.event.dispatch.apply( eventHandle.elem, arguments ).
所以删除就有两种情况 一是把目标事件从缓存的处理列表里删掉, 二是彻底解除绑定

以click事件为例:
如果dom元素上有多个绑定好的click事件,
解除绑定有两种情况, 1是删掉缓存上事件队列里的其中一个,2是事件队列全都删完了 这时才需要removeEventListener或detachEvent

调用过程还是fn.off ==> jQuery.event.remove ==> 第二种情况jQuery.removeEvent

一:解绑的几种用法

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
/**
* 博文作者:ruantao1989{@}gmail.com
* 引自博客:ruantao.duapp.com/blog
*/
//1:绑定俩事件
function handler1() {
alert("h1==>" + $(this).text());
}
function handler2() {
alert("h2==>" + $(this).text());
}
$("p").on("click.rt", handler1);
$("p").on("click.rt", handler2);
//2:两种解绑方式
//通过namespace,把命名空间里处理函数都卸载掉
function off_ns() {
$("p").off("click.rt");
}
//通过handler,解绑指定函数
function off_fn() {
$("p").off("click.rt", handler2);
}
//特殊字符**能解除事件委托
function off_allDelegated() {
$("p").off("click.rt","**");
}

二:对外接口

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
/**
* 博文作者:ruantao1989{@}gmail.com
* 引自博客:ruantao.duapp.com/blog
*/
jQuery.fn.extend({
//第一步
//==>源码3541行
off: function( types, selector, fn ) {
var handleObj, type;
//传入的types里有types.preventDefault和types.handleObj
if ( types && types.preventDefault && types.handleObj ) {
//types里的jQ.Event实例
handleObj = types.handleObj;
jQuery( types.delegateTarget ).off(
handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
handleObj.selector,
handleObj.handler
);
return this;
}
//types若是对象, 遍历逐个off
if ( typeof types === "object" ) {
// ( types-object [, selector] )
for ( type in types ) {
this.off( type, selector, types[ type ] );
}
return this;
}
//处理函数为false或是函数
if ( selector === false || typeof selector === "function" ) {
//fn改为处理函数
fn = selector;
//第二个参数置空
selector = undefined;
}
//不指定解绑函数的情况, fn就是renturn false
if ( fn === false ) {
fn = returnFalse;
}
//遍历选择器出来元素, 挨个remove
return this.each(function() {
jQuery.event.remove( this, types, fn, 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
/**
* 博文作者:ruantao1989{@}gmail.com
* 引自博客:ruantao.duapp.com/blog
*/
jQuery.event = {
//第二步
//==>源码2757行
remove: function( elem, types, handler, selector, mappedTypes ) {
var t, tns, type, origType, namespaces, origCount,
j, events, special, eventType, handleObj,
//如果该元素在cache上有缓存就取出来
elemData = jQuery.hasData( elem ) && jQuery._data( elem );
//elemData是add时候fix过的jQ.Event实例, events数组里是各个事件数组
// events: Object
// --click: Array[2]
// ----0: Object
// ----1: Object
if ( !elemData || !(events = elemData.events) ) {
return;
}
//types可能是空格分隔的字串, 这转成数组
types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
for ( t = 0; t < types.length; t++ ) {
//命名空间和事件数组
tns = rtypenamespace.exec( types[t] ) || [];
type = origType = tns[1];
namespaces = tns[2];
//如果没提供type,就是要解绑全部事件
if ( !type ) {
for ( type in events ) {
//把events里的事件[idx]逐个解绑
jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
}
continue;
}
//是否需要特殊处理
special = jQuery.event.special[ type ] || {};
//判断是否要用delegateType或bindType, 不用就是传入的事件类型
type = ( selector? special.delegateType : special.bindType ) || type;
//eventType是type事件全部的处理函数, 主要就是操作这个数组, 数据结构如下:
// eventType Array[2]
// --0: Object
// --1: Object
// --delegateCount: 0
// --length: 2
eventType = events[ type ] || [];
//处理函数个数
origCount = eventType.length;
//多级ns的切分
namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
//删除cache中, handler的缓存
for ( j = 0; j < eventType.length; j++ ) {
//取出其中一个处理函数
handleObj = eventType[ j ];
if ( ( mappedTypes || origType === handleObj.origType ) &&
( !handler || handler.guid === handleObj.guid ) && //有处理函数,则guid必须相等
( !namespaces || namespaces.test( handleObj.namespace ) ) && //有ns,则判断是对应ns和子ns
( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { // 特殊字符**会删掉每一个处理函数
//核心语句
//删掉缓存里对应事件名下的 这个指定处理函数
//操作缓存上的这个数组, 就是控制触发时的处理函数队列
eventType.splice( j--, 1 );
//委托数--
if ( handleObj.selector ) {
eventType.delegateCount--;
}
//如果有对应special.remove则调用删除
if ( special.remove ) {
special.remove.call( elem, handleObj );
}
}
}
//如果上一个循环把eventType都删干净了, 且之前是有处理函数的(确实是删掉的), 就要做清理
//比如click下没有任何事件了, 才removeEvent.
//因为对click的事件绑定实际上是用elem.addEventListener或elem.attachEvent绑定jQuery.event.dispatch.apply( eventHandle.elem, arguments )
if ( eventType.length === 0 && origCount !== eventType.length ) {
if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
//只有对应事件类型下,一个处理函数都没有 才removeEvent
jQuery.removeEvent( elem, type, elemData.handle );
}
delete events[ type ];
}
}
//如果events对象为空, 也就是用不着了, 就删掉cache里的handle
if ( jQuery.isEmptyObject( events ) ) {
//这个handle是add()中核心的那句jQuery.event.dispatch.apply( eventHandle.elem, arguments )
delete elemData.handle;
//removeData 是缓存系统删除缓存用的
jQuery.removeData( elem, "events", true );
}
},

四: 必要时解除事件的绑定

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
/**
* 博文作者:ruantao1989{@}gmail.com
* 引自博客:ruantao.duapp.com/blog
*/
//==>源码3220行
//事件解绑
jQuery.removeEvent = document.removeEventListener ?
//非IE
function( elem, type, handle ) {
if ( elem.removeEventListener ) {
elem.removeEventListener( type, handle, false );
}
} :
//IE
function( elem, type, handle ) {
var name = "on" + type;
if ( elem.detachEvent ) {
//detachEvent之前把元素上对应事件的元素置空, IE678的兼容
if ( typeof elem[ name ] === "undefined" ) {
elem[ name ] = null;
}
elem.detachEvent( name, handle );
}
};