简要:$.Callbacks是一个生成回调管家Callback的工厂,Callback提供一系列方法来管理一个回调列表($.Callbacks的一个私有变量list),包括添加回调函数,html
删除回调函数等等...,话很少说看正文:数组
var memory, // Last fire value (for non-forgettable lists) fired, // Flag to know if list was already fired //是否回调过 firing, // Flag to know if list is currently firing //回调函数列表是否正在执行中 firingStart, // First callback to fire (used internally by add and fireWith) //第一回调函数的下标,启动回调任务的开始位置 firingLength, // End of the loop when firing //回调函数列表长度? firingIndex, // Index of currently firing callback (modified by remove if needed),正在执行回调函数的索引 list = [], // Actual callback list //回调数据源: 回调列表 stack = !options.once && [], // Stack of fire calls for repeatable lists//回调只能触发一次的时候,stack永远为false
memory的值由传入$.Callbacks的形参对象决定,具备状态记忆功能。当为null||false时,callback.add仅仅是添加方法,而当为true时,则添加以后会当即执行。缓存
请参考以下代码(来自: http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)
function fn1() { console.log(1) } function fn2() { console.log(2) } var callbacks = $.Callbacks('memory'); callbacks.add(fn1); callbacks.fire(); // 必须先fire callbacks.add(fn2); // 此时会当即触发fn2
以下是触发回调任务的底层函数:网络
fire = function(data) { memory = options.memory && data //记忆模式,触发事后,再添加新回调,也当即触发。 fired = true firingIndex = firingStart || 0 //回调任务开始的索引赋值给将要执行函数的索引 firingStart = 0 //回调任务的触发会将列表里剩下的全部函数执行,所以下一次任务触发确定是从0开始的,这里重置一下 firingLength = list.length firing = true //标记正在回调 //遍历回调列表 for ( ; list && firingIndex < firingLength ; ++firingIndex ) { //若是 list[ firingIndex ] 为false,且stopOnFalse(中断)模式 //list[firingIndex].apply(data[0], data[1]) 这是执行回调 if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse) { memory = false //中断回调执行 break } } firing = false //标记回调执行完毕 if (list) { //stack里还缓存有未执行的回调,若是回调任务只能执行一次则stack为false if (stack) stack.length && fire(stack.shift()) //执行stack里的回调 else if (memory) list.length = 0 //memory 清空回调列表 list.length = 0清空数组的技巧 else Callbacks.disable(); //其余状况如 once 禁用回调 } },
fire方法在回调执行前首先初始化回调索引和回调状态,而后循环顺序执行回调函数,传递参数为data[1],以data[0]为上下文执行,执行完后根据once是否只执行一次多线程
,处理和回收list,memory,stack等变量。app
var ca = { name:"tom", age:1 } function printName() { console.log(this.name+"-----"+arguments[0]); } function printAge() { console.log(this.age+"-----"+arguments[0]); } list.push(printName); list.push(printAge); fire([ca,'test']); 结果: tom-----test 1-----test
接下来是建立了一个Callbacks 对象以及一系列方法函数
//添加一个或一组到回调列表里 add: function() { if (list) { //回调列表已存在 var start = list.length, //位置从最后一个开始 add = function(args) { //参数能够是:fn,[fn,fn],fn $.each(args, function(_, arg){ if (typeof arg === "function") { //是函数 //非unique,或者是unique,但回调列表未添加过 if (!options.unique || !Callbacks.has(arg)) list.push(arg) } //是数组/伪数组,添加,从新遍历 else if (arg && arg.length && typeof arg !== 'string') add(arg) }) } //添加进列表 add(arguments) //若是列表正在执行中,修正长度,使得新添加的回调也能够执行, //firing:true代表fire中的循环执行还未结束,此时能够修改length;为false则表示循环执行结束了 if (firing) firingLength = list.length else if (memory) { //memory 模式下,修正开始下标,start为list.length,这里只循环一次 firingStart = start fire(memory) //当即执行全部回调 } } return this }
add方法是将一系列的fn加入到回调列表中,内部的add方法用到了递归技巧,同时对于回调任务的执行期间作出相应处理oop
,以下是例子(参考连接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)this
function fn1() { console.log(1) } function fn2() { console.log(2) } var callbacks = $.Callbacks(); // 方式1 callbacks.add(fn1); // 方式2 一次添加多个回调函数 callbacks.add(fn1, fn2); // 方式3 传数组 callbacks.add([fn1, fn2]); // 方式4 函数和数组掺和 callbacks.add(fn1, [fn2]);
remove方法会从回调列表中删除一个或一组fn(有去重功能)spa
//从回调列表里删除一个或一组回调函数,remove(fn),remove(fn,fn) remove: function() { if (list) { //回调列表存在才能够删除 //_做废参数 //遍历参数 $.each(arguments, function(_, arg){ var index //若是arg在回调列表里 while ((index = $.inArray(arg, list, index)) > -1) { list.splice(index, 1) //执行删除 // Handle firing indexes //回调正在执行中 if (firing) { //避免回调列表溢出 if (index <= firingLength) --firingLength //在正执行的回调函数后,递减结尾下标 if (index <= firingIndex) --firingIndex //在正执行的回调函数前,递减开始下标 } } }) } return this }
方法中的while循环是去除回调列表中重复的函数的技巧,去除指定fn以后,若是此时回调列表正在执行回调任务,则修正回调索引(由于list中全部的回调函数的索引都改变了),这里能传多个fn作参数,它会循环删除,以下例子(参考连接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)
function fn1() { console.log(1) } function fn2() { console.log(2) } var callbacks = $.Callbacks(); callbacks.add(fn1, fn2); callbacks.remove(fn1); //此时fire只会触发fn2了。 var callbacks = $.Callbacks(); callbacks.add(fn1, fn2, fn1, fn2); callbacks.remove(fn1); //此时会把add两次的fn1都删掉,fire时只触发fn2两次。换成if则只删fn1一次
Callbacks里面的函数封装了fire方法,Callbacks.fire(args),回调函数将以Callbacks为this,args为参数调用,stack的做用是若回调列表处于触发状态,此时将
本次要触发的任务信息存入stack中
/** * 用上下文、参数执行列表中的全部回调函数 * @param context * @param args * @returns {*} */ fireWith: function(context, args) { // 未回调过,非锁定、禁用时 if (list && (!fired || stack)) { args = args || [] args = [context, args.slice ? args.slice() : args] if (firing) stack.push(args) //正在回调中 ,存入static else fire(args) //不然当即回调 } return this }, /** * 用参数执行列表中的全部回调函数 * @param context * @param args * @returns {*} */ fire: function() { //执行回调 return Callbacks.fireWith(this, arguments) }
以下:(参考连接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)
function fn() { console.log(this); // 上下文是callbacks console.log(arguments); // [3] } var callbacks = $.Callbacks(); callbacks.add(fn); callback.fire(3); //前面已经提到了,fire方法用来触发回调函数,默认的上下文是callbacks对象, //还能够传参给回调函数。 function fn() { console.log(this); // 上下文是person console.log(arguments); // [3] } var person = {name: 'jack'}; var callbacks = $.Callbacks(); callbacks.add(fn); callback.fireWith(person, 3); //callback.fire.call(person, 3); //其实fire内部调用的是fireWith,只是将上下文指定为this了, //而this正是$.Callbacks构造的对象。
设计思考:
add,remove方法和fire等方法内部都加入了对回调列表的状态的判断和相应处理,好比fireWith方法内部判断当前回调任务是否正在进行,若是是,则将要执行的fn暂时加入到stack中。但这一设计思路对于单线程的js来讲有点不太合理,若是是先执行回调列表,再fireWith,则实际过程是回调任务执行完以后再执行fireWith,这个时候回调任务已经结束了,不可能存在firing的状况。但为什么jser仍是要这么设计呢?
这里的设计是针对多线程来设计的。回调任务开始的同时,允许另外一线程操做并修改回调列表内容。假想这样一个场景:有一个网络机器人R,功能是执行全部线上客户要求执行的一系列操做,某一时刻,客户A上传了一系列操做(命名为DO),当R正在执行操做时,客户A要求此时执行DO,而R正在执行其余操做,此时,R是被锁住的。DO则被加入到缓冲队列中延后执行。
这里在举个例子说明 来自连接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html
// 观察者模式 var observer = { hash: {}, subscribe: function(id, callback) { if (typeof id !== 'string') { return } if (!this.hash[id]) { this.hash[id] = $.Callbacks() this.hash[id].add(callback) } else { this.hash[id].add(callback) } }, publish: function(id) { if (!this.hash[id]) { return } this.hash[id].fire(id) } } // 订阅 observer.subscribe('mailArrived', function() { alert('来信了') }) observer.subscribe('mailArrived', function() { alert('又来信了') }) observer.subscribe('mailSend', function() { alert('发信成功') }) // 发布 setTimeout(function() { observer.publish('mailArrived') }, 5000) setTimeout(function() { observer.publish('mailSend') }, 10000)
结束语:本文素材多来自其余文章并加上了本身的理解,感谢以下两位博客:
http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html
http://www.cnblogs.com/mominger/p/4369469.html