1 // String to Object options format cache
2 var optionsCache = {};
3
4 // Convert String-formatted options into Object-formatted ones and store in cache
5 /*
6 这个函数主要将传入的options字符串封装成对象
7 比如将传入的'once memory'封装成
8 optionsCache['once memory'] = {
9 once : true,
10 memory : true
11 }
12 这样方便下次同样的options复用和判断
13 */
14 function createOptions( options ) {
15 var object = optionsCache[ options ] = {};
16 jQuery.each( options.split( core_rspace ), function( _, flag ) {
17 object[ flag ] = true;
18 });
19 return object;
20 }
21
22 /*
23 * Create a callback list using the following parameters:
24 *
25 * options: an optional list of space-separated options that will change how
26 * the callback list behaves or a more traditional option object
27 *
28 * By default a callback list will act like an event callback list and can be
29 * "fired" multiple times.
30 *
31 * Possible options:
32 *
33 * once: will ensure the callback list can only be fired once (like a Deferred)
34 *
35 * memory: will keep track of previous values and will call any callback added
36 * after the list has been fired right away with the latest "memorized"
37 * values (like a Deferred)
38 *
39 * unique: will ensure a callback can only be added once (no duplicate in the list)
40 *
41 * stopOnFalse: interrupt callings when a callback returns false
42 *
43 */
44 jQuery.Callbacks = function( options ) {
45
46 // Convert options from String-formatted to Object-formatted if needed
47 // (we check in cache first)
48 options = typeof options === "string" ?
49 ( optionsCache[ options ] || createOptions( options ) ) :
50 jQuery.extend( {}, options );
51
52 var // Last fire value (for non-forgettable lists)
53 //大多数情况下这个变量是包含两个元素的数组,[0]表示上次调用的对象,[1]表示上次调用的参数
54 memory,
55 // Flag to know if list was already fired
56 //标识是否执行过回调函数,主要用来实现once
57 fired,
58 // Flag to know if list is currently firing
59 //当前是否在firing,可以参考多线
中锁的概念,主要用在调用回调函数时,对callbacks对象进行add、remove或者fire,后面会有两个单独的例子说明这种情况
60 firing,
61 // First callback to fire (used internally by add and fireWith)
62 firingStart,
63 // End of the loop when firing
64 firingLength,
65 // Index of currently firing callback (modified by remove if needed)
66 firingIndex,
67 // Actual callback list
68 //所有的回调会被push到这个数组
69 list = [],
70 // Stack of fire calls for repeatable lists
71 //结合firing使用,如果有once选项没什么作用,否则当firing为true时将add或者fire的操作临时存入这个变量,以便于循环完list时继续处理这个变量里面的函数队列
72 stack = !options.once && [],
73 // Fire callbacks
74 fire = function( data ) {
75 //如果设置memory为true,则将本次的参数data缓存到memory中,用于下次调用
76 memory = options.memory && data;
77 fired = true;
78 //如果options.memory为true,firingStart为上一次Callbacks.add后回调列表的length值
79 firingIndex = firingStart || 0;
80 firingStart = 0;
81 firingLength = list.length;
82 firing = true;
83 for ( ; list && firingIndex < firingLength; firingIndex++ ) {
84 //如果stopOnFalse为true且本次执行的回调函数返回值为false,则终止回调函数队列的执行
85 if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
86 //设置memory为false,防止调用add时会被fire(这个分支是在stopOnFalse memory时被触发)
87 memory = false; // To prevent further calls using add
88 break;
89 }
90 }
91 firing = false;
92 if ( list ) {
93 //options.once为false(stack的作用见上)
94 if ( stack ) {
95 //存在递归的可能,所以不用使用while
96 if ( stack.length ) {
97 fire( stack.shift() );
98 }
99 //memory = true, memory = true的情况
100 } else if ( memory ) {
101 list = [];
102 } else {
103 //once = true, memory = false的情况
104 self.disable();
105 }
106 }
107 },
108 // Actual Callbacks object
109 self = {
110 // Add a callback or a collection of callbacks to the list
111 add: function() {
112 if ( list ) {
113 // First, we save the current length
114 var start = list.length;
115 (function add( args ) {
116 jQuery.each( args, function( _, arg ) {
117 var type = jQuery.type( arg );
118 if ( type === "function" ) {
119 //实现unique(回调不唯一 或 唯一且不存在,则push)
120 if ( !options.unique || !self.has( arg ) ) {
121 list.push( arg );
122 }
123 //如果arg是数组,递归添加回调
124 } else if ( arg && arg.length && type !== "string" ) {
125 // Inspect recursively
126 add( arg );
127 }
128 });
129 })( arguments );
130 // Do we need to add the callbacks to the
131 // current firing batch?
132 if ( firing ) {
133 firingLength = list.length;
134 // With memory, if we're not firing then
135 // we should call right away
136 //如果memory不是false,则直接每次add的时候都自动fire
137 } else if ( memory ) {
138 firingStart = start;
139 fire( memory );
140 }
141 }
142 return this;
143 },
144 // Remove a callback from the list
145 remove: function() {
146 if ( list ) {
147 jQuery.each( arguments, function( _, arg ) {
148 var index;
149 while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
150 list.splice( index, 1 );
151 // Handle firing indexes
152 //如果在执行Callbacks.remove操作的状态为firing时则更新firingLength和firingIndex的值
153 if ( firing ) {
154 if ( index <= firingLength ) {
155 firingLength--;
156 }
157 //特殊处理,如果移除的回调的索引小于当前正在执行回调的索引,则firingIdex--
158 //后面未执行的回调则得以正常执行
159 if ( index <= firingIndex ) {
160 firingIndex--;
161 }
162 }
163 }
164 });
165 }
166 return this;
167 },
168 // Control if a given callback is in the list
169 has: function( fn ) {
170 return jQuery.inArray( fn, list ) > -1;
171 },
172 // Remove all callbacks from the list
173 empty: function() {
174 list = [];
175 return this;
176 },
177 // Have the list do nothing anymore
178 disable: function() {
179 list = stack = memory = undefined;
180 return this;
181 },
182 // Is it disabled?
183 disabled: function() {
184 return !list;
185 },
186 // Lock the list in its current state
187 lock: function() {
188 stack = undefined;
189 if ( !memory ) {
190 self.disable();
191 }
192 return this;
193 },
194 // Is it locked?
195 locked: function() {
196 return !stack;
197 },
198 // Call all callbacks with the given context and arguments
199 fireWith: function( context, args ) {
200 args = args || [];
201 args = [ context, args.slice ? args.slice() : args ];
202 if ( list && ( !fired || stack ) ) {
203 if ( firing ) {
204 stack.push( args );
205 } else {
206 fire( args );
207 }
208 }
209 return this;
210 },
211 // Call all the callbacks with the given arguments
212 fire: function() {
213 self.fireWith( this, arguments );
214 return this;
215 },
216 // To know if the callbacks have already been called at least once
217 fired: function() {
218 return !!fired;
219 }
220 };
221
222 return self;
223 };
需要特殊注意的是有一个firing这个变量,下面给出这个变量的应用场景:
1、在Callbacks.add中firing为true的情况
1 // 定义三个将要增加到回调列表的回调函数fn1,fn2,fn3
2 function fn1(val){
3 console.log( 'fn1 says ' + val );
4 //此时Callbacks函数内部的firingLength会自动加1,虽然初始化的Callbacks对象有memory选项,
5 //但add并不会立即执行fn2,而是等执行完add前的函数队列之后再执行fn2
6 cbs.add(fn2);
7 }
8 function fn2(val){
9 console.log( 'fn2 says ' + val );
10 }
11 function fn3(val){
12 console.log( 'fn3 says ' + val );
13 }
14
15 // Callbacks传递了memory
16 // 也可以这样使用$.Callbacks({ memory: true });
17 var cbs = $.Callbacks('memory');
18
19 // 将fn1增加到回调列表中,因为在fn1中有执行了add(fn2)操作,因此回调列表中的回调为fn1,fn2
20 cbs.add(fn1);
21
22 //fn1 says foo
23 //fn2 says foo
24 cbs.fire('foo');
25
26 //将之前fire的参数传递给最近增加的回调fn3,并执行fn3
27 //fn3 says foo
28 cbs.add(fn3);
29
30 //再执行一次fire,注意此时回调列表中的回调依次是fn1,fn2,fn3,fn2
31 //fn1 says bar
32 //fn2 says bar
33 //fn3 says bar
34 //fn2 says bar
35 cbs.fire('bar');
复制代码
2、在Callbacks.fireWith中firing为true的情况
复制代码
function fn1(val){
console.log( 'fn1 says ' + val );
}
function fn2(val){
console.log( 'fn2 says ' + val );
//此时并不会立即触发cbs里面的回调,而是先把[window, ['bar']]放入stack里面
//等执行完fireWith前的函数队列之后才执行
cbs.fireWith(window, ['bar']);
//firingLength会减一,一定要将当前的函数remove掉,否则会导致死循环
cbs.remove(fn2);
}
var cbs = $.Callbacks();
cbs.add(fn1);
cbs.add(fn2);
//fn1 says bar
//fn2 says bar
//fn1 says bar
cbs.fire('bar');