// Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT license. /** * 回調函數管理:添加add() 移除remove()、觸發fire()、鎖定lock()、禁用disable()回調函數。它爲Deferred異步隊列提供支持 * 原理:經過一個數組保存回調函數,其餘方法圍繞此數組進行檢測和操做 * * * 標記: * once: 回調只能觸發一次 * memory 記錄上一次觸發回調函數列表時的參數,以後添加的函數都用這參數當即執行 * unique 一個回調函數只能被添加一次 * stopOnFalse 當某個回調函數返回false時中斷執行 */ ;(function($){ // Create a collection of callbacks to be fired in a sequence, with configurable behaviour // Option flags: // - once: Callbacks fired at most one time. // - memory: Remember the most recent context and arguments // - stopOnFalse: Cease iterating over callback list // - unique: Permit adding at most one instance of the same callback $.Callbacks = function(options) { options = $.extend({}, options) 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 /** * 回調底層函數 */ fire = function(data) { memory = options.memory && data //記憶模式,觸發事後,再添加新回調,也當即觸發。 fired = true firingIndex = firingStart || 0 firingStart = 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裏還緩存有未執行的回調 if (stack) stack.length && fire(stack.shift()) //執行stack裏的回調 else if (memory) list.length = 0 //memory 清空回調列表 list.length = 0清空數組的技巧 else Callbacks.disable() //其餘狀況如 once 禁用回調 } }, Callbacks = { //添加一個或一組到回調列表裏 add: function() { if (list) { //回調列表已存在 var start = list.length, //位置從最後一個開始 add = function(args) { $.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) //若是列表正在執行中,修正長度,使得新添加的回調也能夠執行 if (firing) firingLength = list.length else if (memory) { //memory 模式下,修正開始下標, firingStart = start fire(memory) //當即執行全部回調 } } return this }, //從回調列表裏刪除一個或一組回調函數 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 }, /** * 檢查指定的回調函數是否在回調列表中 * @param fn * @returns {boolean} */ has: function(fn) { // return !!(list && (fn ? $.inArray(fn, list) > -1 : list.length)) }, /** * 清空回調函數 * @returns {*} */ empty: function() { firingLength = list.length = 0 return this }, //禁用回調函數 disable: function() { list = stack = memory = undefined return this }, /** * 是否已禁用回調函數 * @returns {boolean} */ disabled: function() { return !list }, /** * 鎖定回調函數 * @returns {*} */ lock: function() { stack = undefined; //致使沒法觸發 //非memory模式下,禁用列表 if (!memory) Callbacks.disable() return this }, /** * 是不是鎖定的 * @returns {boolean} */ locked: function() { return !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) }, /** * 回調列表是否被回調過 * @returns {boolean} */ fired: function() { return !!fired } } return Callbacks } })(Zepto)
用法html
自己單獨用於管理回調函數列表。數組
另外做爲Deferred異步隊列的基礎。瀏覽器
生命週期緩存
設計原理多線程
設計上的疑問:app
列表觸發列表stack是基於,當列表正在觸發函數,而又需正執行添加函數的操做。這意味着兩個線程,線程A在觸發fire列表,線程B往列表add函數。理論上這樣設計是合理的。異步
但實際上,咱們分析下...函數
瀏覽器解析頁面的主要線程以下oop
因爲JS引擎是單線程的,任何JS的執行一個時間片斷只能執行一段代碼。如setTimeout,雖然開闢了計時線程,可是一旦響應時間到了,將執行JS函數時,馬上遵循單線程原則。函數塞入執行隊列。事件線程也同樣。一旦響應時,JS響應函數也會塞入執行隊列。 問題來了,回調列表Callbacks怎麼可能在觸發fire的同時,再add操做呢?只有真正的多線程才能碰到這樣的問題。this
Worker?Work它是沒法訪問本頁面的$.Callbacks對象。由於它訪問不了window對象。
除非它本身再經過importScripts('zepto-callbacks.js')加載一個JS,但它和頁面的Callbacks是兩個不一樣的對象。頁面向Worker發送數據,Worker加載新的Callbacks,執行完畢後,再返回數據給頁面。這多麼麻煩啊?(Worker用法)
因此Callbacks的這個多線程設計到底是基於什麼場景?
舉例
此觀察者案例來自 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)