Callbacks 模塊並非必備的模塊,其做用是管理回調函數,爲 Defferred 模塊提供支持,Defferred 模塊又爲 Ajax 模塊的 promise
風格提供支持,接下來很快就會分析到 Ajax模塊,在此以前,先看 Callbacks 模塊和 Defferred 模塊的實現。javascript
讀 Zepto 源碼系列文章已經放到了github上,歡迎star: reading-zeptohtml
本文閱讀的源碼爲 zepto1.2.0java
將 Callbacks 模塊的代碼精簡後,獲得的結構以下:git
;(function($){
$.Callbacks = function(options) {
...
Callbacks = {
...
}
return Callbacks
}
})(Zepto)複製代碼
其實就是向 zepto
對象上,添加了一個 Callbacks
函數,這個是一個工廠函數,調用這個函數返回的是一個對象,對象內部包含了一系列的方法。github
options
參數爲一個對象,在源碼的內部,做者已經註釋了各個鍵值的含義。數組
// 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
once: 回調至多隻能觸發一次
memory: 記下最近一次觸發的上下文及參數列表,再添加新回調的時候都馬上用這個上下文及參數當即執行
stopOnFalse: 若是隊列中有回調返回 `false`,當即停止後續回調的執行
unique: 同一個回調只能添加一次複製代碼
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複製代碼
options
: 構造函數的配置,默認爲空對象list
: 回調函數列表stack
: 列表能夠重複觸發時,用來緩存觸發過程當中未執行的任務參數,若是列表只能觸發一次,stack
永遠爲 false
memory
: 記憶模式下,會記住上一次觸發的上下文及參數fired
: 回調函數列表已經觸發過firing
: 回調函數列表正在觸發firingStart
: 回調任務的開始位置firingIndex
: 當前回調任務的索引firingLength
:回調任務的長度我用 jQuery
和 Zepto
的時間比較短,以前也沒有直接用過 Callbacks
模塊,單純看代碼不易理解它是怎樣工做的,在分析以前,先看一下簡單的 API
調用,可能會有助於理解。promise
var callbacks = $.Callbacks({memory: true})
var a = function(a) {
console.log('a ' + a)
}
var b = function(b) {
console.log('b ' + b)
}
var c = function(c) {
console.log('c ' + c)
}
callbacks.add(a).add(b).add(c) // 向隊列 list 中添加了三個回調
callbacks.remove(c) // 刪除 c
callbacks.fire('fire')
// 到這步輸出了 `a fire` `b fire` 沒有輸出 `c fire`
callbacks.lock()
callbacks.fire('fire after lock') // 到這步沒有任何輸出
// 繼續向隊列添加回調,注意 `Callbacks` 的參數爲 `memory: true`
callbacks.add(function(d) {
console.log('after lock')
})
// 輸出 `after lock`
callbacks.disable()
callbacks.add(function(e) {
console.log('after disable')
})
// 沒有任何輸出複製代碼
上面的例子只是簡單的調用,也有了註釋,下面開始分析 API
緩存
fire = function(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
break
}
}
firing = false
if (list) {
if (stack) stack.length && fire(stack.shift())
else if (memory) list.length = 0
else Callbacks.disable()
}
}複製代碼
Callbacks
模塊只有一個內部方法 fire
,用來觸發 list
中的回調執行,這個方法是 Callbacks
模塊的核心。微信
memory = options.memory && data
fired = true
firingIndex = firingStart || 0
firingStart = 0
firingLength = list.length
firing = true複製代碼
fire
只接收一個參數 data
,這個內部方法 fire
跟咱們調用 API
所接收的參數不太同樣,這個 data
是一個數組,數組裏面只有兩項,第一項是上下文對象,第二項是回調函數的參數數組。app
若是 options.memory
爲 true
,則將 data
,也即上下文對象和參數保存下來。
將 list
是否已經觸發過的狀態 fired
設置爲 true
。
將當前回調任務的索引值 firingIndex
指向回調任務的開始位置 firingStart
或者回調列表的開始位置。
將回調列表的開始位置 firingStart
設置爲回調列表的開始位置。
將回調任務的長度 firingLength
設置爲回調列表的長度。
將回調的開始狀態 firing
設置爲 true
for ( ; list && firingIndex < firingLength ; ++firingIndex ) {
if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse) {
memory = false
break
}
}
firing = false複製代碼
執行回調的總體邏輯是遍歷回調列表,逐個執行回調。
循環的條件是,列表存在,而且當前回調任務的索引值 firingIndex
要比回調任務的長度要小,這個很容易理解,當前的索引值都超出了任務的長度,就找不到任務執行了。
list[firingIndex].apply(data[0], data[1])
就是從回調列表中找到對應的任務,綁定上下文對象,和傳入對應的參數,執行任務。
若是回調執行後顯式返回 false
, 而且 options.stopOnFalse
設置爲 true
,則停止後續任務的執行,而且清空 memory
的緩存。
回調任務執行完畢後,將 firing
設置爲 false
,表示當前沒有正在執行的任務。
if (list) {
if (stack) stack.length && fire(stack.shift())
else if (memory) list.length = 0
else Callbacks.disable()
}複製代碼
列表任務執行完畢後,先檢查 stack
中是否有沒有執行的任務,若是有,則將任務參數取出,調用 fire
函數執行。後面會看到,stack
儲存的任務是 push
進去的,用 shift
取出,代表任務執行的順序是先進先出。
memory
存在,則清空回調列表,用 list.length = 0
是清空列表的一個方法。在全局參數中,能夠看到, stack
爲 false
,只有一種狀況,就是 options.once
爲 true
的時候,表示任務只能執行一次,因此要將列表清空。而 memory
爲 true
,表示後面添加的任務還能夠執行,因此還必須保持 list
容器的存在,以便後續任務的添加和執行。
其餘狀況直接調用 Callbacks.disable()
方法,禁用全部回調任務的添加和執行。
add: function() {
if (list) {
var start = list.length,
add = function(args) {
$.each(args, function(_, arg){
if (typeof arg === "function") {
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) {
firingStart = start
fire(memory)
}
}
return this
},複製代碼
start
爲原來回調列表的長度。保存起來,是爲了後面修正回調任務的開始位置時用。
add = function(args) {
$.each(args, function(_, arg){
if (typeof arg === "function") {
if (!options.unique || !Callbacks.has(arg)) list.push(arg)
}
else if (arg && arg.length && typeof arg !== 'string') add(arg)
})
}複製代碼
add
方法的做用是將回調函數 push
進回調列表中。參數 arguments
爲數組或者僞數組。
用 $.each
方法來遍歷 args
,獲得數組項 arg
,若是 arg
爲 function
類型,則進行下一個判斷。
在下一個判斷中,若是 options.unique
不爲 true
,即容許重複的回調函數,或者原來的列表中不存在該回調函數,則將回調函數存入回調列表中。
若是 arg
爲數組或僞數組(經過 arg.length
是否存在判斷,而且排除掉 string
的狀況),再次調用 add
函數分解。
add(arguments)
if (firing) firingLength = list.length
else if (memory) {
firingStart = start
fire(memory)
}複製代碼
調用 add
方法,向列表中添加回調函數。
若是回調任務正在執行中,則修正回調任務的長度 firingLength
爲當前任務列表的長度,以便後續添加的回調函數能夠執行。
不然,若是爲 memory
模式,則將執行回調任務的開始位置設置爲 start
,即原來列表的最後一位的下一位,也就是新添加進列表的第一位,而後調用 fire
,以緩存的上下文及參數 memory
做爲 fire
的參數,當即執行新添加的回調函數。
remove: function() {
if (list) {
$.each(arguments, function(_, arg){
var index
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
},複製代碼
刪除列表中指定的回調。
用 each
遍歷參數列表,在 each
遍歷裏再有一層 while
循環,循環的終止條件以下:
(index = $.inArray(arg, list, index)) > -1複製代碼
$.inArray()
最終返回的是數組項在數組中的索引值,若是不在數組中,則返回 -1
,因此這個判斷是肯定回調函數存在於列表中。關於 $.inArray
的分析,見《讀zepto源碼之工具函數》。
而後調用 splice
刪除 list
中對應索引值的數組項,用 while
循環是確保列表中有重複的回調函數都會被刪除掉。
if (firing) {
if (index <= firingLength) --firingLength
if (index <= firingIndex) --firingIndex
}複製代碼
若是回調任務正在執行中,由於回調列表的長度已經有了變化,須要修正回調任務的控制參數。
若是 index <= firingLength
,即回調函數在當前的回調任務中,將回調任務數減小 1
。
若是 index <= firingIndex
,即在正在執行的回調函數前,將正在執行函數的索引值減小 1
。
這樣作是防止回調函數執行到最後時,沒有找到對應的任務執行。
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
},複製代碼
以指定回調函數的上下文的方式來觸發回調函數。
fireWith
接收兩個參數,第一個參數 context
爲上下文對象,第二個 args
爲參數列表。
fireWith
後續執行的條件是列表存在而且回調列表沒有執行過或者 stack
存在(可爲空數組),這個要注意,後面講 disable
方法和 lock
方法區別的時候,這是一個很重要的判斷條件。
args = args || []
args = [context, args.slice ? args.slice() : args]複製代碼
先將 args
不存在時,初始化爲數組。
再從新組合成新的變量 args
,這個變量的第一項爲上下文對象 context
,第二項爲參數列表,調用 args.slice
是對數組進行拷貝,由於 memory
會儲存上一次執行的上下文對象及參數,應該是怕外部對引用的更改的影響。
if (firing) stack.push(args)
else fire(args)複製代碼
若是回調正處在觸發的狀態,則將上下文對象和參數先儲存在 stack
中,從內部函數 fire
的分析中能夠得知,回調函數執行完畢後,會從 stack
中將 args
取出,再觸發 fire
。
不然,觸發 fire
,執行回調函數列表中的回調函數。
add
和 remove
都要判斷 firing
的狀態,來修正回調任務控制變量,fire
方法也要判斷 firing
,來判斷是否須要將 args
存入 stack
中,可是 javascript
是單線程的,照理應該不會出如今觸發的同時 add
或者 remove
或者再調用 fire
的狀況。
fire: function() {
return Callbacks.fireWith(this, arguments)
},複製代碼
fire
方法,用得最多,可是卻很是簡單,調用的是 fireWidth
方法,上下文對象是 this
。
has: function(fn) {
return !!(list && (fn ? $.inArray(fn, list) > -1 : list.length))
},複製代碼
has
有兩個做用,若是有傳參時,用來查測所傳入的 fn
是否存在於回調列表中,若是沒有傳參時,用來檢測回調列表中是否已經有了回調函數。
fn ? $.inArray(fn, list) > -1 : list.length複製代碼
這個三元表達式前面的是判斷指定的 fn
是否存在於回調函數列表中,後面的,若是 list.length
大於 0
,則回調列表已經存入了回調函數。
empty: function() {
firingLength = list.length = 0
return this
},複製代碼
empty
的做用是清空回調函數列表和正在執行的任務,可是 list
還存在,還能夠向 list
中繼續添加回調函數。
disable: function() {
list = stack = memory = undefined
return this
},複製代碼
disable
是禁用回調函數,實質是將回調函數列表置爲 undefined
,同時也將 stack
和 memory
置爲 undefined
,調用 disable
後,add
、remove
、fire
、fireWith
等方法再也不生效,這些方法的首要條件是 list
存在。
disabled: function() {
return !list
},複製代碼
回調是否已經被禁止,其實就是檢測 list
是否存在。
lock: function() {
stack = undefined
if (!memory) Callbacks.disable()
return this
},複製代碼
鎖定回調列表,實際上是禁止 fire
和 fireWith
的執行。
實際上是將 stack
設置爲 undefined
, memory
不存在時,調用的是 disable
方法,將整個列表清空。效果等同於禁用回調函數。fire
和 add
方法都不能再執行。
爲何 memory
存在時,stack
爲 undefined
就能夠將列表的 fire
和 fireWith
禁用掉呢?在上文的 fireWith
中,我特別提到了 !fired || stack
這個判斷條件。在 stack
爲 undefined
時,fireWith
的執行條件看 fired
這個條件。若是回調列表已經執行過, fired
爲 true
,fireWith
不會再執行。若是回調列表沒有執行過,memory
爲 undefined
,會調用 disable
方法禁用列表,fireWith
也不能執行。
因此,disable
和 lock
的區別主要是在 memory
模式下,回調函數觸發事後,lock
還能夠調用 add
方法,向回調列表中添加回調函數,添加完畢後會馬上用 memory
的上下文和參數觸發回調函數。
locked: function() {
return !stack
},複製代碼
回調列表是否被鎖定。
其實就是檢測 stack
是否存在。
fired: function() {
return !!fired
}複製代碼
回調列表是否已經被觸發過。
回調列表觸發一次後 fired
就會變爲 true
,用 !!
的目的是將 undefined
轉換爲 false
返回。
最後,全部文章都會同步發送到微信公衆號上,歡迎關注,歡迎提意見:
做者:對角另外一面