Jquery中文网 www.jquerycn.cn
Jquery中文网 >  jQuery  >  jquery 教程  >  正文 jQuery 2.0.3 源码分析 回调对象 – Callbacks

jQuery 2.0.3 源码分析 回调对象 – Callbacks

发布时间:2016-09-06   编辑:www.jquerycn.cn
jquery中文网为您提供jQuery 2.0.3 源码分析 回调对象 – Callbacks等资源,欢迎您收藏本站,我们将为您提供最新的jQuery 2.0.3 源码分析 回调对象 – Callbacks资源

源码API:http://api.jquery.com/jQuery.Callbacks/

jQuery.Callbacks()是在版本1.7中新加入的。它是一个多用途的回调函数列表对象,提供了一种强大的方法来管理回调函数队列。

 

那么jQuery.Callbacks使用场景在哪里?

在很多时候需要控制一系列的函数顺序执行。那么一般就需要一个队列函数来处理这个问题

我们看一段代码

function Aaron(List, callback) {     setTimeout(function() {       var task = List.shift();       task(); //执行函数       if (task.length > 0) {  //递归分解         setTimeout(arguments.callee, 1000)       } else {         callback()       }     }, 25)   }    Aaron([function(){     alert('a')   },function(){     alert('b')   }],function(){     alert('callback')   })
 
分别弹出 ‘a’ , ‘b’ ,’callback’

传入一组函数参数,靠递归解析,分个执行,其实就是靠setTimeout可以把函数加入到队列末尾才执行的原理

*****但是这样写,是不是很麻烦?*****

 

我们换成jQuery提供的方式

var callbacks = $.Callbacks();    callbacks.add(function() {     alert('a');   })    callbacks.add(function() {     alert('b');   })    callbacks.fire(); //输出结果: 'a' 'b'

是不是便捷很多了,代码又很清晰,所以它是一个多用途的回调函数列表对象,提供了一种强大的方法来管理回调函数队列。

 

同时还提供几个便捷的处理参数

  • once: 确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred).
  • memory: 保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred).
  • unique: 确保一次只能添加一个回调(所以在列表中没有重复的回调).
  • stopOnFalse: 当一个回调返回false 时中断调用
var callbacks = $.Callbacks('once');    callbacks.add(function() {     alert('a');   })    callbacks.add(function() {     alert('b');   })    callbacks.fire(); //输出结果: 'a' 'b'   callbacks.fire(); //未执行

once的作用是使callback队列只执行一次

OK,我们大概知道这个是干嘛用的了,可以开始上正菜了。

 


$.Callbacks是在jQuery内部使用,如为$.ajax,$.Deferred等组件提供基础功能的函数,jQuery在1.5引入了Deferred对象(异步列队),jQuery内部基本所有有异步的代码都被promise所转化成同步代码执行了,后期在讨论了

根据jQuery.Callbacks()的API

提供一下几种方法:

callbacks.add()        回调列表中添加一个回调或回调的集合。 callbacks.disable()    禁用回调列表中的回调 callbacks.disabled()   确定回调列表是否已被禁用。  callbacks.empty()      从列表中删除所有的回调. callbacks.fire()       用给定的参数调用所有的回调 callbacks.fired()      访问给定的上下文和参数列表中的所有回调。  callbacks.fireWith()   访问给定的上下文和参数列表中的所有回调。 callbacks.has()        确定列表中是否提供一个回调 callbacks.lock()       锁定当前状态的回调列表。 callbacks.locked()     确定回调列表是否已被锁定。 callbacks.remove()     从回调列表中的删除一个回调或回调集合。

 

我们看官网提供的demo

function fn1( value ) {     console.log( value ); }   function fn2( value ) {     fn1("fn2 says: " + value);     return false; }

可以将上述两个方法作为回调函数,并添加到 $.Callbacks 列表中,并按下面的顺序调用它们:

var callbacks = $.Callbacks(); callbacks.add( fn1 );   // outputs: foo! callbacks.fire( "foo!" );   callbacks.add( fn2 );   // outputs: bar!, fn2 says: bar! callbacks.fire( "bar!" );

这样做的结果是,当构造复杂的回调函数列表时,将会变更很简单。可以根据需要,很方面的就可以向这些回调函数中传入所需的参数。

上面的例子中,我们使用了 $.Callbacks() 的两个方法: .add().fire()。 .add() 可以向回调函数列表中添加新的回调函数,fire() 可以向回调函数中传递参数,并执行回调函数。

 

设计思想:

先看官网的demo这个列子,涉及到了 add 与 fire方法,熟悉设计模式的童鞋呢,一眼就看出,其实又是基于发布订阅的观察者模式的设计了

pub/sub (观察者模式) 的背后,总的想法是在应用程序中增强松耦合性。并非是在其它对象的方法上的单个对象调用。一个对象作为特定任务或是另一对象的活动的观察者,并且在这个任务或活动发生时,通知观察者。观察者也被叫作订阅者(Subscriber),它指向被观察的对象,既被观察者(Publisher 或 subject)。当事件发生时,被观察者(Publisher)就会通知观察者(subscriber)

作为 $.Callbacks() 的创建组件的一个演示,只使用回调函数列表,就可以实现 Pub/Sub 系统。将 $.Callbacks 作为一个队列

 

我来模拟下常规最简单的实现

var Observable = {       callbacks: [],       add: function(fn) {         this.callbacks.push(fn);       },       fire: function() {         this.callbacks.forEach(function(fn) {           fn();         })       }   }

Observable.add(function() {
  alert(1)
})
Observable.fire(function() {
  alert(2)
})

Observable.fire(); // 1, 2

构建一个存放回调的数组,如this.callbacks= [] 添加回调时,将回调push进this.callbacks,执行则遍历this.callbacks执行回调。

也弹出1跟2了,实际上jQuery.callbacks是如何处理的呢?

 


我们看源码
整个$.Callbacks的源码很少,它是一个工厂函数,使用函数调用(非new,它不是一个类)创建对象,它有一个可选参数flags用来设置回调函数的行为。、

对外的接口也就是self的返回

jQuery 2.0.3 源码分析 回调对象 - Callbacks

 

self上的add源码

jQuery 2.0.3 源码分析 回调对象 - CallbacksjQuery 2.0.3 源码分析 回调对象 - Callbacks
展开

源码

   add: function() {     if ( list ) {      // First, we save the current length      var start = list.length;       (function add( args ) {       jQuery.each( args, function( _, arg ) {        var type = jQuery.type( arg );        if ( type === "function" ) {         if ( !options.unique || !self.has( arg ) ) {          list.push( arg );         }        } else if ( arg && arg.length && type !== "string" ) {         // Inspect recursively         add( arg );        }       });      })( arguments );       // Do we need to add the callbacks to the      // current firing batch?      if ( firing ) {       firingLength = list.length;      // With memory, if we're not firing then      // we should call right away      } else if ( memory ) {       firingStart = start;       fire( memory );      }     }     return this;    },

其中有一段代码要单独拿出来

//这里用了一个立即执行的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 )

 

add方法

实参可以是Function, Array

callbacks.add( callbacks )
  • callbacks

    类型: Function, Array

    一个函数,或者一个函数数组,用来添加到回调列表。

如果是数组会递归调用私有的add函数 list.push( arg );

发现没,设计的原理上其实跟上面发的简单模式 大同小异

 

fire方法

外观模式 self.fire –> self.fireWith –> fire

最终执行代码是内部私有的fire方法了

jQuery 2.0.3 源码分析 回调对象 - CallbacksjQuery 2.0.3 源码分析 回调对象 - Callbacks
fire方法        // 触发回调函数列表         fire = function( data ) {             //如果参数memory为true,则记录data             memory = options.memory && data;             //标记触发回调             fired = true;             firingIndex = firingStart || 0;             firingStart = 0;             firingLength = list.length;             //标记正在触发回调             firing = true;             for ( ; list && firingIndex < firingLength; firingIndex++ ) {                 if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {                     memory = false; // 阻止未来可能由于add所产生的回调                     break;    //由于参数stopOnFalse为true,所以当有回调函数返回值为false时退出循环                 }             }             //标记回调结束             firing = false;             //如果列表存在             if ( list ) {                 //如果堆栈存在                 if ( stack ) {                     //如果堆栈不为空                     if ( stack.length ) {                         //从堆栈头部取出,递归fire。                         fire( stack.shift() );                     }                 //否则,如果有记忆                 } else if ( memory ) {                     //列表清空                     list = [];                 //再否则阻止回调列表中的回调                 } else {                     self.disable();                 }             }         },

最终处理的代码

list[ firingIndex ].apply( data[ 0 ], data[ 1 ] )

其实就是拿出list中保存的回调函数,执行罢了,所以整个设计的原理,还是符合我们开始设想的

 


具体的实现

$.Callbacks( "once" )

确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred).

var callbacks = $.Callbacks( "once" ); callbacks.add( fn1 ); callbacks.fire( "foo" );  //foo 只执行了一次,后面没执行了 callbacks.add( fn2 ); callbacks.fire( "bar" ); callbacks.remove( fn2 ); callbacks.fire( "foobar" ); 

在fire中调用了 self.disable(); 方法

// 禁用回调列表中的回调。 disable: function() {     list = stack = memory = undefined;     return this; },

 


$.Callbacks( "memory" )

保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred).

var callbacks = $.Callbacks("memory");    callbacks.add(function() {     console.log("f1");   });    callbacks.fire(); //输出 "f1",这时函数列表已经执行完毕!    callbacks.add(function() {     console.log("f2");   }); //memory作用在这里,没有fire,一样有结果: f2

在调用 add() 方法时,如果这时 callbacks队列 满足 fired && firing = false(真执行完毕) && memory(需要在构造函数指定),那么add() 进去的回调函数会立即执行,而这个 add 进去的回调函数调用时的参数存储在 memory 变量中。memory 变量用于存储最后一次调用 callbacks.fireWith(…) 时所使用的参数 [context, arguments]。

 


$.Callbacks( "unique" )

确保一次只能添加一个回调(所以在列表中没有重复的回调)

var f1 = function() {           console.log("f1");        };         var callbacks = $.Callbacks();        callbacks.add(f1);        callbacks.add(f1);        callbacks.fire();         //输出  f1 f1         //传递参数 "unique"        callbacks = $.Callbacks("unique");        callbacks.add(f1);     //有效        callbacks.add(f1);     //添加不进去        callbacks.fire();     //输出: f1

****注意add方法默认不去重,比如这里fn1添加两次,fire时会触发两次****

这里处理很简单

if ( !options.unique || !self.has( arg ) ) { //确保是否可以重复     list.push( arg ); }                }

在添加的到处理队列时候,判断一下即可

 


$.Callbacks( "stopOnFalse" ):

当一个回调返回false 时中断调用

var f1 = function() {     console.log("f1");     return false   }; //注意 return false;   var f2 = function() {     console.log("f2");   };    var callbacks = $.Callbacks();   callbacks.add(f1);   callbacks.add(f2);   callbacks.fire(); //输出 f1 f2    callbacks = $.Callbacks("memory stopOnFalse");   callbacks.add(f1);   callbacks.add(f2);   callbacks.fire();     //只输出  f1    callbacks.add(function() {     console.log("f3");   }); //不会输出,memory已经失去作用了   callbacks.fire();     //重新触发,输出f1

 

附源码:

jQuery.Callbacks = function( options ) {      // Convert options from String-formatted to Object-formatted if needed     // (we check in cache first)     //通过字符串在optionsCache寻找有没有相应缓存,如果没有则创建一个,有则引用     //如果是对象则通过jQuery.extend深复制后赋给options。     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为true,则记录data             memory = options.memory && data;             fired = true; //标记触发回调             firingIndex = firingStart || 0;             firingStart = 0;             firingLength = list.length;             //标记正在触发回调             firing = true;             for ( ; list && firingIndex < firingLength; firingIndex++ ) {                 if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {                     // 阻止未来可能由于add所产生的回调                     memory = false; // To prevent further calls using add                     break; //由于参数stopOnFalse为true,所以当有回调函数返回值为false时退出循环                 }             }             //标记回调结束             firing = false;             if ( list ) {                 if ( stack ) {                     if ( stack.length ) {                         //从堆栈头部取出,递归fire                         fire( stack.shift() );                     }                 } else if ( memory ) {//否则,如果有记忆                     list = [];                 } else {//再否则阻止回调列表中的回调                     self.disable();                 }             }         },         // Actual Callbacks object         // 暴露在外的Callbacks对象,对外接口         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;                     (function add( args ) { //jQuery.each,对args传进来的列表的每一个对象执行操作                         jQuery.each( args, function( _, arg ) {                             var type = jQuery.type( arg );                             if ( type === "function" ) {                                 if ( !options.unique || !self.has( arg ) ) { //确保是否可以重复                                     list.push( arg );                                 }                             //如果是类数组或对象,递归                             } else if ( arg && arg.length && type !== "string" ) {                                 // Inspect recursively                                 add( arg );                             }                         });                     })( arguments );                     // Do we need to add the callbacks to the                     // current firing batch?                     // 如果回调列表中的回调正在执行时,其中的一个回调函数执行了Callbacks.add操作                     // 上句话可以简称:如果在执行Callbacks.add操作的状态为firing时                     // 那么需要更新firingLength值                     if ( firing ) {                         firingLength = list.length;                     // With memory, if we're not firing then                     // we should call right away                     } else if ( memory ) {                         //如果options.memory为true,则将memory做为参数,应用最近增加的回调函数                         firingStart = start;                         fire( memory );                     }                 }                 return this;             },             // Remove a callback from the list             // 从函数列表中删除函数(集)             remove: function() {                 if ( list ) {                     jQuery.each( arguments, function( _, arg ) {                         var index;                         // while循环的意义在于借助于强大的jQuery.inArray删除函数列表中相同的函数引用(没有设置unique的情况)                         // jQuery.inArray将每次返回查找到的元素的index作为自己的第三个参数继续进行查找,直到函数列表的尽头                         // splice删除数组元素,修改数组的结构                         while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {                             list.splice( index, 1 );                             // Handle firing indexes                             // 在函数列表处于firing状态时,最主要的就是维护firingLength和firgingIndex这两个值                             // 保证fire时函数列表中的函数能够被正确执行(fire中的for循环需要这两个值                             if ( firing ) {                                 if ( index <= firingLength ) {                                     firingLength--;                                 }                                 if ( index <= firingIndex ) {                                     firingIndex--;                                 }                             }                         }                     });                 }                 return this;             },             // Check if a given callback is in the list.             // If no argument is given, return whether or not list has callbacks attached             // 回调函数是否在列表中.             has: function( fn ) {                 return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );             },             // Remove all callbacks from the list             // 从列表中删除所有回调函数             empty: function() {                 list = [];                 firingLength = 0;                 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 ) {                 if ( list && ( !fired || stack ) ) {                     args = args || [];                     args = [ context, args.slice ? args.slice() : args ];                     //如果正在回调                     if ( firing ) {                         //将参数推入堆栈,等待当前回调结束再调用                         stack.push( args );                     } else {//否则直接调用                         fire( args );                     }                 }                 return this;             },             // Call all the callbacks with the given arguments             // 以给定的参数调用所有回调函数             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; };

jQuery.Callbacks() 比较简单,也没什么难点

jQuery.Callbacks() 方法的核心是 fire() 方法,将该 fire() 方法作为私有方法被封装在函数中不可直接访问

因此像 memory、firing、fired 这些状态对于外部上下文来说是不可更改的

还有需要注意的是,如果回调函数中使用了 this 对象,可以直接用这个 this 来访问self对象的公有API。当然,也可以用 fireWith() 自己指定 this 的引用对象。

jQuery.Callbacks()的核心思想是 Pub/Sub 模式,建立了程序间的松散耦合和高效通信。

 

PS: 路过留点痕。。

您可能感兴趣的文章:
jQuery源码分析系列
jQuery 2.0.3 源码分析 回调对象 – Callbacks
jQuery 2.0.3 源码分析 Deferred(最细的实现剖析,带图)
jQuery 2.0.3 源码分析 事件体系结构
jQuery 2.0.3 源码分析 Deferred概念
jQuery源码分析系列:Callback深入
jQuery源码分析系列(31) : Ajax deferred实现
jQuery 2.0.3 源码分析 样式操作
jQuery 2.0.3 源码分析core – 整体架构
jQuery 2.0.3 源码分析Sizzle引擎 – 高效查询

[关闭]