jQuery.Callbacks()提供的回调函数队列管理本来是延时回调处理的一部分,但是后面将其独立出来作为一个模块。jQuery就是这样,各个模块间的代码耦合度是处理的比较好的,值得学习。虽然是从延时回调处理中独立出来的,但是它的功能非常强大,提供了一种强大的方法来管理回调函数队列。
大家都明白封装函数的目的:去耦合与简化操作。
通常情况下函数队列的处理方式
//执行函数 function runList(arr){ for(var i = 0; i < arr.length; i++){ arr[i](); }
arr.length = 0; } var list = []; //添加函数队列 list[list.length] = function(){alert(1)}; list[list.length] = function(){alert(2)};
list[list.length] = function(){alert(3)};
//执行
runList(list);//三个函数顺序执行
使用$.callbacks封装以后的处理为
var callbacks = $.Callbacks("unique"); callbacks.add( function(){alert(1)} ); callbacks.add( function(){alert(2)} ); callbacks.add( function(){alert(3)} ); //执行 callbacks.fire();//三个函数顺序执行
干净了很多。而且代码可读性比最开始的那个要好很多。list[list.length]神马的最讨厌了。还有主要的是$.callbacks有四个属性可以组合,这个组合可就很强大了。
a. Callbacks的四个可设置的属性分析
once: 确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred).
设置“once”在执行第一次fire后会直接禁用该Callbacks(fire函数代码段else {self.disable();})
var f1 = function(value) { console.log(value); }; var callbacks = $.Callbacks('once'); callbacks.add(f1);//无执行结果,添加一个回调 callbacks.fire(1);//执行结果1。清除回调列表 callbacks.add(f1);//没有添加回调直接返回 callbacks.fire(2);//无执行结果 callbacks.add(f1);//没有添加回调直接返回 callbacks.fire(3);//无执行结果
memory: 保持以前的值(参数),将函数添加到这个列表的后面,并使用先前保存的参数立即执行该函数。 内部变量会保存上次执行的场景。
他有一个特点,就是在第一次fire之前使用add添加的回调都不会马上执行,只有调用了一次fire之后使用add添加的回调会马上执行。该设置本身不会清除之前的回调列表。
需要注意的是每次add内部执行fire函数都会将firingStart置为0,只有下次add的时候会从新设置firingStart的值。
eg:
var f1 = function(value) { console.log(value); }; var callbacks = $.Callbacks("memory"); callbacks.add( fn1 );//无执行结果 callbacks.fire( "1" );//执行结果1。保存场景参数1 callbacks.add( fn1 );//执行结果1。使用上次保存的场景参数1 callbacks.fire( "2" );//执行结果2,2。保存场景参数2 callbacks.add( fn1 );//执行结果2。使用上次保存的场景参数2 callbacks.fire( "3" );//执行结果3,3,3。保存场景参数3 callbacks.add( fn1 );//执行结果3。使用上次保存的场景参数3 callbacks.fire( "4" );//执行结果4,4,4,4。保存场景参数4
组合使用,组合使用中间使用空格隔开
设置“once memory”, options.once=options.memory=true。在执行第一次fire后会把回到列表清空,而且之后每次add马上执行后页同样会把回调列表清空(fire函数代码段else if ( memory ) {list = [];})。
eg:
var f1 = function(value) { console.log(value); }; var callbacks = $.Callbacks('once memory'); callbacks.add(f1);//无执行结果,添加一个回调 callbacks.fire(1);//执行结果1。清除回调列表,保存场景参数1 callbacks.add(f1);//添加一个回调并执行结果1,使用上次保存的场景参数。清除回调列表 callbacks.fire(2);//无执行结果 callbacks.add(f1);//添加一个回调并执行结果1,使用上次保存的场景参数。清除回调列表 callbacks.fire(3);//无执行结果
两个设置之间用空格,不支持其他符号,比如设置“once,memory”等同于没有设置。
eg:
var f1 = function(value) { console.log(value); }; var callbacks = $.Callbacks('once,memory'); callbacks.add(f1);//无执行结果,添加一个回调 callbacks.fire(1);//执行结果1 callbacks.add(f1);//添加一个回调 callbacks.fire(2);//执行结果2,2 callbacks.add(f1);//添加一个回调 callbacks.fire(3);//执行结果3,3,3 callbacks.add(f1);//添加一个回调 callbacks.fire(4);//执行结果4,4,4,4
unique: 确保一次只能添加一个回调(所以在列表中没有重复的回调).
stopOnFalse: 当一个回调返回false 时中断调用
当有一个回调返回false的时候,会设置memory为false。导致memory失去作用(后续add的函数不会马上执行,当然先前memory保证了前面执行过得函数不再执行这也条也就不起作用了。下次fire会从回调列表的第一个开始执行)。
b. 整体结构
使用缓存是jQuery中最常见的技巧。$.Callbacks中也不例外。主要是缓存Callbacks中遇到的选项(字符串)。
// 使用过的选项缓存 var optionsCache = {}; // 新增和缓存回调设置于optionsCache中 function createOptions( options ) { var object = optionsCache[ options ] = {}; jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { object[ flag ] = true; }); return object; } jQuery.Callbacks = function( options ) { // 尽可能读取缓存,没有则新增缓存 options = typeof options === "string" ? ( optionsCache[ options ] || createOptions( options ) ) : jQuery.extend( {}, options ); var // 回调列表正在执行(为true的时候)的标志 firing, // 最后执行的值(为memory选项下保存) memory, // 回调已经被执行过的标志 fired, // 循环执行回调列表的结束位置 firingLength, // 当前真正执行的回调的索引值 (执行下个回调的时候回更改【如果必要的话】) firingIndex, // 循环执行回调列表的开始位置(在函数add和fireWith中使用) firingStart, // 回调列表 list = [], // Stack记录要重复执行的回调列表 stack = !options.once && [], // data数组一般第一个元素是上下文环境,第二个元素是参数 //执行回调列表 fire = function( data ) {…}, // 回调对象 self = { // 添加回调 add: function() {…}, // 移除回调 remove: function() {…}, ... // 给定 context 和 arguments执行所有回调 fireWith: function( context, args ) { args = args || []; //组装args,第一个元素为上下文环境,第二个元素为参数列表 args = [ context, args.slice ? args.slice() : args ]; //有list且函数列表没有被执行过或者存在要循环执行的函数列表 if ( list && ( !fired || stack ) ) { //如果正在fire,则把函数场景记录在stack中 if ( firing ) { stack.push( args ); //否则,至此那个fire } else { fire( args ); } } return this; }, // 使用给定的arguments执行所有回调 fire: function() { self.fireWith( this, arguments ); return this; }, ... }; return self; };
下面分析两个最重要的两个函数,添加回调函数add和执行回调函数fire
c. add:添加回调
添加回调函数比较简单,针对可能传递的值(函数或者函数数组)将回调添加到回调列表中即可,这里使用了一个闭包,使用了外部变量list。
(function add( args ) { jQuery.each( args, function( _, arg ) { var type = jQuery.type( arg ); if ( type === "function" ) { //当$.Callbacks('unique')时,保证列表里面不会出现重复的回调 if ( !options.unique || !self.has( arg ) ) { list.push( arg ); } //如果是数组则递归添加 } else if ( arg && arg.length && type !== "string" ) { add( arg ); } }); })( arguments );
但是这里需要对用户初始化设置的属性做一些特殊的处理。
如果列表没有定义或null(一般只有在用户设置once且执行过一次后list才会白置为未定义),直接返回list
//如果列表没有定义或null(一般只有在用户设置once且执行过一次后list才会白置为未定义) if ( list ) { ... } return this;
当有回调真正执行的时候,需要重新设定回调列表的结束位置firingLength,使后续添加的函数也会执行。实际上这个功能很受争议,不过正常情况一般不会出现添加函数的时候正在执行某个回调。
还有一个比较重要的判断:对于设置了'memory'选项并fire过了回调列表,并且没有还在等待中的回调要fire,则应当马上执行新添加的回调(执行fire(memory))
// 如果正在fire,则设定要执行结束的点firingLength,使后续添加的函数最后不会执行 if ( firing ) { firingLength = list.length; // 对于memory(设置了'memory' option并fire过了,memory才能通过该else if语句), //如果没有回调真正fire,应当马上执行fire(memory)。 } else if ( memory ) { //这里保证了前面执行过得函数不再执行 firingStart = start; fire( memory ); }
完整的源码如下
add: function() { //如果列表没有定义或null(一般只有在用户设置once且执行过一次后list才会白置为未定义) if ( list ) { // 保存当前list长度,为memory处理备用 var start = list.length; (function add( args ) { jQuery.each( args, function( _, arg ) { var type = jQuery.type( arg ); if ( type === "function" ) { //当$.Callbacks('unique')时,保证列表里面不会出现重复的回调 if ( !options.unique || !self.has( arg ) ) { list.push( arg ); } //如果是数组则递归添加 } else if ( arg && arg.length && type !== "string" ) { add( arg ); } }); })( arguments ); // 如果正在fire,则设定要执行结束的点firingLength,使后续添加的函数最后执行 if ( firing ) { firingLength = list.length; // 对于memory(设置了'memory' option并fire过了,memory才能通过该else if语句), //如果我们后续没有fire,应当马上执行fire(memory)。 } else if ( memory ) { //这里保证了前面执行过得函数不再执行 firingStart = start; fire( memory ); } } return this; }