jQuery源码阅读12-1:回调队列$.Callback对象

Deferred这个东西异常nb, 应该是jQuery中异步处理的核心
因为没有实现这种东西的经验, 所以不太好理解, 好多代码看不出目的性
有点儿操作系统里任务控制的感觉, 还得弄个状态机 三四种运行状态循环 管理运行状态什么的…
“回调队列”这个专题被迫拆成两部分吧, 先读简单的Callbacks队列

一:用法示例:

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
//具体执行
window.blog = "/blog"
var foo = function(value1, value2) {
console.log('foo:' + value1 + ',' + value2 + this.blog); //默认this是Callbacks实例
}
var bar = function(value1, value2) {
console.log('bar:' + value1 + ',' + value2);
}
//创建Callbacks对象
var callbacks = $.Callbacks();
//1.加到队列
callbacks.add(foo);
callbacks.add(bar);
//2.清空队列
//callbacks.empty();
//3.判断是否在队列之中
console.log("has foo==>" + callbacks.has(foo));
console.log("has bar==>" + callbacks.has(bar));
callbacks.fire("ruantao", "duapp.com");
//4.查看是否被触发过
console.log("have been fired==>" + callbacks.fired());
//5.更换作用域
var Obj = {
blog: "/blog IN Obj"
}
callbacks.fireWith(Obj, ['foo', 'bar']);
//6.删除
callbacks.remove(foo);//接收数组
//7.锁定回调
callbacks.lock();
callbacks.fire();
console.log("have been locked==>" + callbacks.locked());
//8.$.Callbacks(接受参数);
var callbacks = $.Callbacks('unique memory');

二:用Callback实现观察者模式

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
//官方文档里推荐的观察者模式:
var topics = {};
jQuery.Topic = function( id ) {
var callbacks,
method,
topic = id && topics[ id ];
if ( !topic ) {
callbacks = jQuery.Callbacks();
topic = {
publish: callbacks.fire,
subscribe: callbacks.add,
unsubscribe: callbacks.remove
};
if ( id ) {
topics[ id ] = topic;
}
}
return topic;
};
//很漂亮的发布,订阅
// Subscribers
$.Topic( "mailArrived" ).subscribe( fn1 );
$.Topic( "mailArrived" ).subscribe( fn2 );
$.Topic( "mailSent" ).subscribe( fn1 );
// Publisher
$.Topic( "mailArrived" ).publish( "hello world!" );
$.Topic( "mailSent" ).publish( "woo! mail!" );
// Here, "hello world!" gets pushed to fn1 and fn2
// when the "mailArrived" notification is published
// with "woo! mail!" also being pushed to fn1 when
// the "mailSent" notification is published.
/*
output:
hello world!
fn2 says: hello world!
woo! mail!
*/

三:源码阅读

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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
//==>源码908行
/**
* 博文作者:ruantao1989{@}gmail.com
* 引自博客:ruantao.duapp.com/blog
*/
var optionsCache = {};
//把选项字串组装成对象,对应参数为true
function createOptions( options ) {
var object = optionsCache[ options ] = {};
jQuery.each( options.split( core_rspace ), function( _, flag ) {
object[ flag ] = true;
});
return object;
}
//==>源码941行
// once: 确保这个回调列表只执行一次(像一个递延 Deferred).
// memory: 保持以前的值和将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred).
// unique: 确保一次只能添加一个回调(所以有没有在列表中的重复).
// stopOnFalse: 当一个回调返回false 时中断调用
// 默认情况下,回调列表将像事件的回调列表中可以多次触发。
jQuery.Callbacks = function( options ) {
//$.Callbacks接受('unique memory')参数, 转成options对象, 如 options{unique:true,memory:true}
options = typeof options === "string" ?
( optionsCache[ options ] || createOptions( options ) ) :
jQuery.extend( {}, options );
var //最后触发的值(for non-forgettable lists),会记录上次运行到哪
memory,
//队列是否已经执行
fired,
//队列是否正在运行
firing,
//Callbacks列表运行时,开始循环的第一个回调函数(used internally by add and fireWith)
firingStart,
//触发回调列表的长度
firingLength,
//当前正在运行的Callbacks的索引下标
firingIndex,
//回调函数列表
list = [],
//有once=>fasle, 没once=>[], 这个stack是运行期间动态加进来的东西,fire末尾会统一处理
stack = !options.once && [],
//执行触发,核心处理过程
fire = function( data ) {
//开memory模式, 且有[ context, args.slice ? args.slice() : args ]
memory = options.memory && data;
//标记为已触发
fired = true;
//之前有firingStart就沿用(memory模式会记录起始位置), 要不就是0
firingIndex = firingStart || 0;
//清零
firingStart = 0;
//本次运行的队列长度
firingLength = list.length;
//标记为正在运行
firing = true;
//
for ( ; list && firingIndex < firingLength; firingIndex++ ) {
//data数据结构: [ context, args.slice?args.slice():args ]
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
//如果是stopOnFalse模式, 且回调返回false, 就停止运行
memory = false; //关闭memory模式
break;
}
}
//运行结束
firing = false;
//非disable
if ( list ) {
//stack有值, 这个数组应该是在运行期间加入进来的,遍历出列
if ( stack ) {
if ( stack.length ) {
//出列
fire( stack.shift() );
}
//非once模式
} else if ( memory ) {
list = [];
} else {
self.disable();
}
}
},
//jQuery.Callbacks()所返回的对象
self = {
//加入队列
add: function() {
//list在创建对象是初始化为[],此处检查未被disable
if ( list ) {
//当前的队列长度
var start = list.length;
//闭包保存函数
(function add( args ) {
jQuery.each( args, function( _, arg ) {
//遍历传参的类型
var type = jQuery.type( arg );
//是否传入一个函数
if ( type === "function" ) {
//如果是unique模式,列表中已有的就不要加入列表了
if ( !options.unique || !self.has( arg ) ) {
//入列
list.push( arg );
}
//非函数,检查是否是类数组
} else if ( arg && arg.length && type !== "string" ) {
//要是类数组, 再迭代一次
add( arg );
}
});
})( arguments );
//队列这时如果正在运行,用firingLength记录队列长度
if ( firing ) {
firingLength = list.length;
//未运行,且在memory模式下
} else if ( memory ) {
//firingStart改为当前队列末尾,memory模式在这里记录循环的起始点
firingStart = start;
fire( memory );
}
}
//链式,返回self
return this;
},
// Remove a callback from the list
remove: function() {
//检查未被disable
if ( list ) {
jQuery.each( arguments, function( _, arg ) {
var index;
//队列中存在此函数
while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
//删除该元素
list.splice( index, 1 );
//如果正在运行,需处理length
if ( firing ) {
//因为减1了,判断长度
if ( index <= firingLength ) {
firingLength--;
}
if ( index <= firingIndex ) {
firingIndex--;
}
}
}
});
}
return this;
},
//检查队列中有无
has: function( fn ) {
return jQuery.inArray( fn, list ) > -1;
},
//清空全部,这个比较暴力,那几个length也没处理
empty: function() {
list = [];
return this;
},
//禁止, add,remove都是检查if(list)的
disable: function() {
list = stack = memory = undefined;
return this;
},
//是否被禁
disabled: function() {
return !list;
},
//锁定,仅把stack置空
lock: function() {
stack = undefined;
//非memory模式直接disable
if ( !memory ) {
self.disable();
}
return this;
},
//是否被锁
locked: function() {
return !stack;
},
//执行触发,可以更换作用域
fireWith: function( context, args ) {
args = args || [];
//args[]的第一个元素是作用域, 往后:
//如果有args.slice,就是数组的话,返回副本
//非数组就是本身
args = [ context, args.slice ? args.slice() : args ];
//非disable 且
//如果运行过了, 必须要有stack(也就是非锁定,非once),将上下文环境和参数存储
if ( list && ( !fired || stack ) ) {
if ( firing ) {
//正在运行, 就入stack列,在Callback.fire的末尾会执行
stack.push( args );
} else {
//没运行着,直接fire
fire( args );
}
}
return this;
},
//运行, 作用域是this, 也就是self实例
fire: function() {
self.fireWith( this, arguments );
return this;
},
//检查标志位,是否已被触发
fired: function() {
return !!fired;
}
};
return self;
};