学习jQuery.Callbacks

深入解析jQuery Callbacks源码
本文详细解读jQuery Callbacks的实现原理与源码细节,包括如何使用Callbacks、其内部构造与执行流程,并通过代码解释重点方法add和fire的实现过程。

转自:http://www.cnblogs.com/littledu/articles/2811728.html

Callbacks是JQ的一个回调对象,可以用来添加回调,执行回调,删除回调等等。并提供一些参数如once,memory,unique等来进行特殊需求的控制。这里就不举例说明Callbacks的用法了。具体详细说明可以参见:http://api.jquery.com/jQuery.Callbacks/

我们学习源码,需先了解如何使用,这里假设我们已经知道如何使用Callbacks了。

他的实现思路就是: 构建一个存放回调的数组,如var list = [],通过闭包使这条回调数组保持存在。添加回调时,将回调push进list,执行则遍历list执行回调。

看思路貌似很简单,我们就直接来看源码吧,对于源码的阅读,我的习惯是大概过一次源码,看看注释了解下大概是作何功用,再直接运行最简单的示例,跟踪下它的执行流程,这个过程中各变量的变化,返回值等等。

这里,在过源码的时候,我们就知道当调用了var cal = $.Callbacks()的时候,返回来的是一个对象(self),这个对象里面实现了add,remove,fire等方法,也就是说,可以用cal.add(fn)来添加回调,cal.remove()来删除回调。

好吧,直接上代码解释,只解释了add和fire两个重要方法,其他都较简单。

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
// String to Object options format cache
var  optionsCache = {};
   
// Convert String-formatted options into Object-formatted ones and store in cache
function  createOptions( options ) {
     var  object = optionsCache[ options ] = {};
     jQuery.each( options.split( core_rspace ), function ( _, flag ) {
         object[ flag ] = true ;
     });
     return  object;
}
   
/*
  * Create a callback list using the following parameters:
  *
  *  options: an optional list of space-separated options that will change how
  *          the callback list behaves or a more traditional option object
  *
  * By default a callback list will act like an event callback list and can be
  * "fired" multiple times.
  *
  * Possible options:
  *
  *  once:           will ensure the callback list can only be fired once (like a Deferred)
  *
  *  memory:         will keep track of previous values and will call any callback added
  *                  after the list has been fired right away with the latest "memorized"
  *                  values (like a Deferred)
  *
  *  unique:         will ensure a callback can only be added once (no duplicate in the list)
  *
  *  stopOnFalse:    interrupt callings when a callback returns false
  *
  */
jQuery.Callbacks = function ( options ) {
   
     // Convert options from String-formatted to Object-formatted if needed
     // (we check in cache first)
     // options 这里设计为一个对象,这一步就是先判断下options缓存里有没这个对象存在,有直接拿来用,没有则通过createOptions方法将传过来的字符串转化为一个对象,对象形式如下:
     // options = { 'once': true,'memory': true}
     options = typeof  options === "string"  ?
         ( optionsCache[ options ] || createOptions( options ) ) :
         jQuery.extend( {}, options );
   
     var  // Last fire value (for non-forgettable lists)
         memory,
         // Flag to know if list was already fired
         fired,
         // Flag to know if list is currently firing
         firing,
         // First callback to fire (used internally by add and fireWith)
         firingStart,
         // End of the loop when firing
         firingLength,
         // Index of currently firing callback (modified by remove if needed)
         firingIndex,
         // Actual callback list
         list = [],
         // Stack of fire calls for repeatable lists
         stack = !options.once && [],
         // Fire callbacks
         fire = function ( data ) {
             memory = options.memory && data;  //当$.Callbacks('memory')时,保存data,data为数组[context,args]
             fired = true ;   //表示已经执行,用于表示队列里的回调已经执行过一次
             firingIndex = firingStart || 0;   //执行队列的下标,相当于普通循环的i,当$.Callbacks('memory')时,需设置firingIndex
             firingStart = 0;                  //重置队列起始值
             firingLength = list.length;       //保存队列的长度
             firing = true ;                    //标示正在执行中
             for  ( ; list && firingIndex < firingLength; firingIndex++ ) {  //
                 //遍历回调数组,执行每一个回调,当回调返回false且有传入stopOnFalse时,也就是$.Callbacks('stopOnFalse'),中止后面回调的执行. PS:这个循环每次都判断list,这个没有必要,可以提到外面判断一次即可
                 if  ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false  && options.stopOnFalse ) {
                     memory = false ; // To prevent further calls using add //当$.Callbacks('memory stopOnFalse')时,memory的作用将失效
                     break ;
                 }
             }
             firing = false //函数执行完后,将执行中的标示设为false
             if  ( list ) {
                 if  ( stack ) {  //执行完回调后,看一下stack是否有回调,有拿出来执行
                     if  ( stack.length ) {
                         fire( stack.shift() );
                     }
                 } else  if  ( memory ) {  //如果没有stack,证明传了once,这里的Callbacks会是这样:$.Callbacks('once memory')
                     list = [];
                 } else  {                //当是$.Callbacks('once')的时候
                     self.disable();
                 }
             }
         },
         // Actual Callbacks object
         self = {
             // Add a callback or a collection of callbacks to the list
             add: function () {
                 if  ( list ) {
                     // First, we save the current length
                     var  start = list.length;  //添加回调函数之前,先保存当前回调函数列表的长度,主要用于当Callbacks传入memory参数时
   
                     //这里用了一个立即执行的add函数来添加回调
                     //直接遍历传过来的arguments进行push
                     ( function  add( args ) {
                         jQuery.each( args, function ( _, arg ) {
                             var  type = jQuery.type( arg );
                             //如果所传参数为函数,则push
                             if  ( type === "function"  ) {
                                 if  ( !options.unique || !self.has( arg ) ) {  //当$.Callbacks('unique')时,保证列表里面不会出现重复的回调
                                     list.push( arg );
                                 }
                             } else  if  ( arg && arg.length && type !== "string"  ) {  //假如传过来的参数为数组或array-like,则继续调用添加,从这里可以看出add的传参可以有add(fn),add([fn1,fn2]),add(fn1,fn2)
                                 // Inspect recursively
                                 add( arg );
                             }
                         });
                     })( arguments );
   
                     // Do we need to add the callbacks to the
                     // current firing batch?
                     // 这里是我不解的地方,firing是标识回调数组正在执行中,也就是fire正在执行,那这里就重置回调数组的长度,但我不知道什么样的代码下这里会执行到
                     if  ( firing ) {
                         firingLength = list.length;
                     // With memory, if we're not firing then
                     // we should call right away
                     // 在fire方法里,只有options.memory为真时,memory才有值,所以,这里的memory保存的是上一次fire时的memory值,而这个的作用就是要立即执行新添加的回调,让新添加的回调也能输出之前fire时传的值。
                     //这里也是$.Callbacks('memory')这个参数作用的地方,有了这个参数,每次add也会执行一次memory
                     } else  if  ( memory ) {  
                         firingStart = start;   //上面保存的start值产生作用的地方
                         fire( memory );
                     }
                 }
                 return  this ;
             },
             // Remove a callback from the list
             remove: function () {
                 if  ( list ) {
                     jQuery.each( arguments, function ( _, arg ) {
                         var  index;
                         while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                             list.splice( index, 1 );
                             // Handle firing indexes
                             if  ( firing ) {
                                 if  ( index <= firingLength ) {
                                     firingLength--;
                                 }
                                 if  ( index <= firingIndex ) {
                                     firingIndex--;
                                 }
                             }
                         }
                     });
                 }
                 return  this ;
             },
             // Control if a given callback is in the list
             has: function ( fn ) {
                 return  jQuery.inArray( fn, list ) > -1;
             },
             // Remove all callbacks from the list
             empty: function () {
                 list = [];
                 return  this ;
             },
             // Have the list do nothing anymore
             disable: function () {
                 list = stack = memory = undefined;
                 return  this ;
             },
             // Is it disabled?
             disabled: function () {
                 return  !list;
             },
             // Lock the list in its current state
             lock: function () {
                 stack = undefined;
                 if  ( !memory ) {
                     self.disable();
                 }
                 return  this ;
             },
             // Is it locked?
             locked: function () {
                 return  !stack;
             },
             // Call all callbacks with the given context and arguments
             fireWith: function ( context, args ) {
                 args = args || [];
                 args = [ context, args.slice ? args.slice() : args ];  //slice只用于字符串或数组,这里为什么要判断一下是不是字符串或数组呢,不明
                 if  ( list && ( !fired || stack ) ) {  //fired表示已经执行过,如果已经执行过了,就要看stack了,他为真才会继续执行
                     if  ( firing ) {  //firing表示执行中,如果是在执行中,则将其推入stack,stack在这里相当于一个缓存数组,用于当fire忙时暂存下回调,但我也是整不出一段代码让这里执行到,暂时不明白
                         stack.push( args );
                     } else  {
                         fire( args );  //执行回调
                     }
                 }
                 return  this ;
             },
             // Call all the callbacks with the given arguments
             // 直接调用fire,回调执行的上下文是self,而上面的fireWidth则可以通过传入context改变回调的执行上下文
             fire: function () {
                 self.fireWith( this , arguments );
                 return  this ;
             },
             // To know if the callbacks have already been called at least once
             fired: function () {
                 return  !!fired;
             }
         };
   
     return  self;
};
(1)普通用户端(全平台) 音乐播放核心体验: 个性化首页:基于 “听歌历史 + 收藏偏好” 展示 “推荐歌单(每日 30 首)、新歌速递、相似曲风推荐”,支持按 “场景(通勤 / 学习 / 运动)” 切换推荐维度。 播放页功能:支持 “无损音质切换、倍速播放(0.5x-2.0x)、定时关闭、歌词逐句滚动”,提供 “沉浸式全屏模式”(隐藏冗余控件,突出歌词与专辑封面)。 多端同步:自动同步 “播放进度、收藏列表、歌单” 至所有登录设备(如手机暂停后,电脑端打开可继续播放)。 音乐发现与管理: 智能搜索:支持 “歌曲名 / 歌手 / 歌词片段” 搜索,提供 “模糊匹配(如输入‘晴天’联想‘周杰伦 - 晴天’)、热门搜索词推荐”,结果按 “热度 / 匹配度” 排序。 歌单管理:创建 “公开 / 私有 / 加密” 歌单,支持 “批量添加歌曲、拖拽排序、一键分享到社交平台”,系统自动生成 “歌单封面(基于歌曲风格配色)”。 音乐分类浏览:按 “曲风(流行 / 摇滚 / 古典)、语言(国语 / 英语 / 日语)、年代(80 后经典 / 2023 新歌)” 分层浏览,每个分类页展示 “TOP50 榜单”。 社交互动功能: 动态广场:查看 “关注的用户 / 音乐人发布的动态(如‘分享新歌感受’)、好友正在听的歌曲”,支持 “点赞 / 评论 / 转发”,可直接点击动态中的歌曲播放。 听歌排行:个人页展示 “本周听歌 TOP10、累计听歌时长”,平台定期生成 “全球 / 好友榜”(如 “好友中你本周听歌时长排名第 3”)。 音乐圈:加入 “特定曲风圈子(如‘古典音乐爱好者’)”,参与 “话题讨论(如‘你心中最经典的钢琴曲’)、线上歌单共创”。 (2)音乐人端(创作者中心) 作品管理: 音乐上传:支持 “无损音频(FLAC/WAV)+ 歌词文件(LRC)+ 专辑封面” 上传,填写 “歌曲信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值