簡要:$.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