摘要:是禁用回調(diào)函數(shù),實質(zhì)是將回調(diào)函數(shù)列表置為,同時也將和置為,調(diào)用后,等方法不再生效,這些方法的首要條件是存在。效果等同于禁用回調(diào)函數(shù)。
Callbacks 模塊并不是必備的模塊,其作用是管理回調(diào)函數(shù),為 Defferred 模塊提供支持,Defferred 模塊又為 Ajax 模塊的 promise 風(fēng)格提供支持,接下來很快就會分析到 Ajax模塊,在此之前,先看 Callbacks 模塊和 Defferred 模塊的實現(xiàn)。
讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡迎star: reading-zepto
源碼版本本文閱讀的源碼為 zepto1.2.0
整體結(jié)構(gòu)將 Callbacks 模塊的代碼精簡后,得到的結(jié)構(gòu)如下:
;(function($){ $.Callbacks = function(options) { ... Callbacks = { ... } return Callbacks } })(Zepto)
其實就是向 zepto 對象上,添加了一個 Callbacks 函數(shù),這個是一個工廠函數(shù),調(diào)用這個函數(shù)返回的是一個對象,對象內(nèi)部包含了一系列的方法。
options 參數(shù)為一個對象,在源碼的內(nèi)部,作者已經(jīng)注釋了各個鍵值的含義。
// 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: 回調(diào)至多只能觸發(fā)一次 memory: 記下最近一次觸發(fā)的上下文及參數(shù)列表,再添加新回調(diào)的時候都立刻用這個上下文及參數(shù)立即執(zhí)行 stopOnFalse: 如果隊列中有回調(diào)返回 `false`,立即中止后續(xù)回調(diào)的執(zhí)行 unique: 同一個回調(diào)只能添加一次全局變量
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 : 構(gòu)造函數(shù)的配置,默認為空對象
list : 回調(diào)函數(shù)列表
stack : 列表可以重復(fù)觸發(fā)時,用來緩存觸發(fā)過程中未執(zhí)行的任務(wù)參數(shù),如果列表只能觸發(fā)一次,stack 永遠為 false
memory : 記憶模式下,會記住上一次觸發(fā)的上下文及參數(shù)
fired : 回調(diào)函數(shù)列表已經(jīng)觸發(fā)過
firing : 回調(diào)函數(shù)列表正在觸發(fā)
firingStart : 回調(diào)任務(wù)的開始位置
firingIndex : 當(dāng)前回調(diào)任務(wù)的索引
firingLength:回調(diào)任務(wù)的長度
基礎(chǔ)用法我用 jQuery 和 Zepto 的時間比較短,之前也沒有直接用過 Callbacks 模塊,單純看代碼不易理解它是怎樣工作的,在分析之前,先看一下簡單的 API 調(diào)用,可能會有助于理解。
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 中添加了三個回調(diào) callbacks.remove(c) // 刪除 c callbacks.fire("fire") // 到這步輸出了 `a fire` `b fire` 沒有輸出 `c fire` callbacks.lock() callbacks.fire("fire after lock") // 到這步?jīng)]有任何輸出 // 繼續(xù)向隊列添加回調(diào),注意 `Callbacks` 的參數(shù)為 `memory: true` callbacks.add(function(d) { console.log("after lock") }) // 輸出 `after lock` callbacks.disable() callbacks.add(function(e) { console.log("after disable") }) // 沒有任何輸出
上面的例子只是簡單的調(diào)用,也有了注釋,下面開始分析 API
內(nèi)部方法 firefire = 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 模塊只有一個內(nèi)部方法 fire ,用來觸發(fā) list 中的回調(diào)執(zhí)行,這個方法是 Callbacks 模塊的核心。
變量初始化memory = options.memory && data fired = true firingIndex = firingStart || 0 firingStart = 0 firingLength = list.length firing = true
fire 只接收一個參數(shù) data ,這個內(nèi)部方法 fire 跟我們調(diào)用 API 所接收的參數(shù)不太一樣,這個 data 是一個數(shù)組,數(shù)組里面只有兩項,第一項是上下文對象,第二項是回調(diào)函數(shù)的參數(shù)數(shù)組。
如果 options.memory 為 true ,則將 data,也即上下文對象和參數(shù)保存下來。
將 list 是否已經(jīng)觸發(fā)過的狀態(tài) fired 設(shè)置為 true。
將當(dāng)前回調(diào)任務(wù)的索引值 firingIndex 指向回調(diào)任務(wù)的開始位置 firingStart 或者回調(diào)列表的開始位置。
將回調(diào)列表的開始位置 firingStart 設(shè)置為回調(diào)列表的開始位置。
將回調(diào)任務(wù)的長度 firingLength 設(shè)置為回調(diào)列表的長度。
將回調(diào)的開始狀態(tài) firing 設(shè)置為 true
執(zhí)行回調(diào)for ( ; list && firingIndex < firingLength ; ++firingIndex ) { if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse) { memory = false break } } firing = false
執(zhí)行回調(diào)的整體邏輯是遍歷回調(diào)列表,逐個執(zhí)行回調(diào)。
循環(huán)的條件是,列表存在,并且當(dāng)前回調(diào)任務(wù)的索引值 firingIndex 要比回調(diào)任務(wù)的長度要小,這個很容易理解,當(dāng)前的索引值都超出了任務(wù)的長度,就找不到任務(wù)執(zhí)行了。
list[firingIndex].apply(data[0], data[1]) 就是從回調(diào)列表中找到對應(yīng)的任務(wù),綁定上下文對象,和傳入對應(yīng)的參數(shù),執(zhí)行任務(wù)。
如果回調(diào)執(zhí)行后顯式返回 false, 并且 options.stopOnFalse 設(shè)置為 true ,則中止后續(xù)任務(wù)的執(zhí)行,并且清空 memory 的緩存。
回調(diào)任務(wù)執(zhí)行完畢后,將 firing 設(shè)置為 false,表示當(dāng)前沒有正在執(zhí)行的任務(wù)。
檢測未執(zhí)行的回調(diào)及清理工作if (list) { if (stack) stack.length && fire(stack.shift()) else if (memory) list.length = 0 else Callbacks.disable() }
列表任務(wù)執(zhí)行完畢后,先檢查 stack 中是否有沒有執(zhí)行的任務(wù),如果有,則將任務(wù)參數(shù)取出,調(diào)用 fire 函數(shù)執(zhí)行。后面會看到,stack 儲存的任務(wù)是 push 進去的,用 shift 取出,表明任務(wù)執(zhí)行的順序是先進先出。
memory 存在,則清空回調(diào)列表,用 list.length = 0 是清空列表的一個方法。在全局參數(shù)中,可以看到, stack 為 false ,只有一種情況,就是 options.once 為 true 的時候,表示任務(wù)只能執(zhí)行一次,所以要將列表清空。而 memory 為 true ,表示后面添加的任務(wù)還可以執(zhí)行,所以還必須保持 list 容器的存在,以便后續(xù)任務(wù)的添加和執(zhí)行。
其他情況直接調(diào)用 Callbacks.disable() 方法,禁用所有回調(diào)任務(wù)的添加和執(zhí)行。
.add()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 為原來回調(diào)列表的長度。保存起來,是為了后面修正回調(diào)任務(wù)的開始位置時用。
內(nèi)部方法addadd = 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 方法的作用是將回調(diào)函數(shù) push 進回調(diào)列表中。參數(shù) arguments 為數(shù)組或者偽數(shù)組。
用 $.each 方法來遍歷 args ,得到數(shù)組項 arg,如果 arg 為 function 類型,則進行下一個判斷。
在下一個判斷中,如果 options.unique 不為 true ,即允許重復(fù)的回調(diào)函數(shù),或者原來的列表中不存在該回調(diào)函數(shù),則將回調(diào)函數(shù)存入回調(diào)列表中。
如果 arg 為數(shù)組或偽數(shù)組(通過 arg.length 是否存在判斷,并且排除掉 string 的情況),再次調(diào)用 add 函數(shù)分解。
修正回調(diào)任務(wù)控制變量add(arguments) if (firing) firingLength = list.length else if (memory) { firingStart = start fire(memory) }
調(diào)用 add 方法,向列表中添加回調(diào)函數(shù)。
如果回調(diào)任務(wù)正在執(zhí)行中,則修正回調(diào)任務(wù)的長度 firingLength 為當(dāng)前任務(wù)列表的長度,以便后續(xù)添加的回調(diào)函數(shù)可以執(zhí)行。
否則,如果為 memory 模式,則將執(zhí)行回調(diào)任務(wù)的開始位置設(shè)置為 start ,即原來列表的最后一位的下一位,也就是新添加進列表的第一位,然后調(diào)用 fire ,以緩存的上下文及參數(shù) memory 作為 fire 的參數(shù),立即執(zhí)行新添加的回調(diào)函數(shù)。
.remove()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 },
刪除列表中指定的回調(diào)。
刪除回調(diào)函數(shù)用 each 遍歷參數(shù)列表,在 each 遍歷里再有一層 while 循環(huán),循環(huán)的終止條件如下:
(index = $.inArray(arg, list, index)) > -1
$.inArray() 最終返回的是數(shù)組項在數(shù)組中的索引值,如果不在數(shù)組中,則返回 -1,所以這個判斷是確定回調(diào)函數(shù)存在于列表中。關(guān)于 $.inArray 的分析,見《讀zepto源碼之工具函數(shù)》。
然后調(diào)用 splice 刪除 list 中對應(yīng)索引值的數(shù)組項,用 while 循環(huán)是確保列表中有重復(fù)的回調(diào)函數(shù)都會被刪除掉。
修正回調(diào)任務(wù)控制變量if (firing) { if (index <= firingLength) --firingLength if (index <= firingIndex) --firingIndex }
如果回調(diào)任務(wù)正在執(zhí)行中,因為回調(diào)列表的長度已經(jīng)有了變化,需要修正回調(diào)任務(wù)的控制參數(shù)。
如果 index <= firingLength ,即回調(diào)函數(shù)在當(dāng)前的回調(diào)任務(wù)中,將回調(diào)任務(wù)數(shù)減少 1 。
如果 index <= firingIndex ,即在正在執(zhí)行的回調(diào)函數(shù)前,將正在執(zhí)行函數(shù)的索引值減少 1 。
這樣做是防止回調(diào)函數(shù)執(zhí)行到最后時,沒有找到對應(yīng)的任務(wù)執(zhí)行。
.fireWithfireWith: 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 },
以指定回調(diào)函數(shù)的上下文的方式來觸發(fā)回調(diào)函數(shù)。
fireWith 接收兩個參數(shù),第一個參數(shù) context 為上下文對象,第二個 args 為參數(shù)列表。
fireWith 后續(xù)執(zhí)行的條件是列表存在并且回調(diào)列表沒有執(zhí)行過或者 stack 存在(可為空數(shù)組),這個要注意,后面講 disable 方法和 lock 方法區(qū)別的時候,這是一個很重要的判斷條件。
args = args || [] args = [context, args.slice ? args.slice() : args]
先將 args 不存在時,初始化為數(shù)組。
再重新組合成新的變量 args ,這個變量的第一項為上下文對象 context ,第二項為參數(shù)列表,調(diào)用 args.slice 是對數(shù)組進行拷貝,因為 memory 會儲存上一次執(zhí)行的上下文對象及參數(shù),應(yīng)該是怕外部對引用的更改的影響。
if (firing) stack.push(args) else fire(args)
如果回調(diào)正處在觸發(fā)的狀態(tài),則將上下文對象和參數(shù)先儲存在 stack 中,從內(nèi)部函數(shù) fire 的分析中可以得知,回調(diào)函數(shù)執(zhí)行完畢后,會從 stack 中將 args 取出,再觸發(fā) fire 。
否則,觸發(fā) fire,執(zhí)行回調(diào)函數(shù)列表中的回調(diào)函數(shù)。
add 和 remove 都要判斷 firing 的狀態(tài),來修正回調(diào)任務(wù)控制變量,fire 方法也要判斷 firing ,來判斷是否需要將 args 存入 stack 中,但是 javascript 是單線程的,照理應(yīng)該不會出現(xiàn)在觸發(fā)的同時 add 或者 remove 或者再調(diào)用 fire 的情況。
.fire()fire: function() { return Callbacks.fireWith(this, arguments) },
fire 方法,用得最多,但是卻非常簡單,調(diào)用的是 fireWidth 方法,上下文對象是 this 。
.has()has: function(fn) { return !!(list && (fn ? $.inArray(fn, list) > -1 : list.length)) },
has 有兩個作用,如果有傳參時,用來查測所傳入的 fn 是否存在于回調(diào)列表中,如果沒有傳參時,用來檢測回調(diào)列表中是否已經(jīng)有了回調(diào)函數(shù)。
fn ? $.inArray(fn, list) > -1 : list.length
這個三元表達式前面的是判斷指定的 fn 是否存在于回調(diào)函數(shù)列表中,后面的,如果 list.length 大于 0 ,則回調(diào)列表已經(jīng)存入了回調(diào)函數(shù)。
.empty()empty: function() { firingLength = list.length = 0 return this },
empty 的作用是清空回調(diào)函數(shù)列表和正在執(zhí)行的任務(wù),但是 list 還存在,還可以向 list 中繼續(xù)添加回調(diào)函數(shù)。
.disable()disable: function() { list = stack = memory = undefined return this },
disable 是禁用回調(diào)函數(shù),實質(zhì)是將回調(diào)函數(shù)列表置為 undefined ,同時也將 stack 和 memory 置為 undefined ,調(diào)用 disable 后,add 、remove 、fire 、fireWith 等方法不再生效,這些方法的首要條件是 list 存在。
.disabled()disabled: function() { return !list },
回調(diào)是否已經(jīng)被禁止,其實就是檢測 list 是否存在。
.lock()lock: function() { stack = undefined if (!memory) Callbacks.disable() return this },
鎖定回調(diào)列表,其實是禁止 fire 和 fireWith 的執(zhí)行。
其實是將 stack 設(shè)置為 undefined , memory 不存在時,調(diào)用的是 disable 方法,將整個列表清空。效果等同于禁用回調(diào)函數(shù)。fire 和 add 方法都不能再執(zhí)行。
.lock() 和 .disable() 的區(qū)別為什么 memory 存在時,stack 為 undefined 就可以將列表的 fire 和 fireWith 禁用掉呢?在上文的 fireWith 中,我特別提到了 !fired || stack 這個判斷條件。在 stack 為 undefined 時,fireWith 的執(zhí)行條件看 fired 這個條件。如果回調(diào)列表已經(jīng)執(zhí)行過, fired 為 true ,fireWith 不會再執(zhí)行。如果回調(diào)列表沒有執(zhí)行過,memory 為 undefined ,會調(diào)用 disable 方法禁用列表,fireWith 也不能執(zhí)行。
所以,disable 和 lock 的區(qū)別主要是在 memory 模式下,回調(diào)函數(shù)觸發(fā)過后,lock 還可以調(diào)用 add 方法,向回調(diào)列表中添加回調(diào)函數(shù),添加完畢后會立刻用 memory 的上下文和參數(shù)觸發(fā)回調(diào)函數(shù)。
.locked()locked: function() { return !stack },
回調(diào)列表是否被鎖定。
其實就是檢測 stack 是否存在。
.fired()fired: function() { return !!fired }
回調(diào)列表是否已經(jīng)被觸發(fā)過。
回調(diào)列表觸發(fā)一次后 fired 就會變?yōu)?true,用 !! 的目的是將 undefined 轉(zhuǎn)換為 false 返回。
系列文章讀Zepto源碼之代碼結(jié)構(gòu)
讀 Zepto 源碼之內(nèi)部方法
讀Zepto源碼之工具函數(shù)
讀Zepto源碼之神奇的$
讀Zepto源碼之集合操作
讀Zepto源碼之集合元素查找
讀Zepto源碼之操作DOM
讀Zepto源碼之樣式操作
讀Zepto源碼之屬性操作
讀Zepto源碼之Event模塊
讀Zepto源碼之IE模塊
參考Zepto源碼分析-callbacks模塊
讀jQuery之十九(多用途回調(diào)函數(shù)列表對象)
License最后,所有文章都會同步發(fā)送到微信公眾號上,歡迎關(guān)注,歡迎提意見:
作者:對角另一面
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/84241.html
摘要:為的項,取出來的分別為和,所以上的和方法,調(diào)用的是中的方法,實質(zhì)是往各自的回調(diào)列表中添加回調(diào)函數(shù)。進度回調(diào)函數(shù)數(shù)組。參數(shù)為異步對象的索引值,參數(shù)為對應(yīng)的上下文數(shù)組,即或,為對應(yīng)的回調(diào)函數(shù)數(shù)組,即或。 Deferred 模塊也不是必備的模塊,但是 ajax 模塊中,要用到 promise 風(fēng)格,必需引入 Deferred 模塊。Deferred 也用到了上一篇文章《讀Zepto源碼之C...
摘要:讀源碼系列文章已經(jīng)放到了上,歡迎源碼版本本文閱讀的源碼為改寫原有的方法模塊改寫了以上這些方法,這些方法在調(diào)用的時候,會為返回的結(jié)果添加的屬性,用來保存原來的集合。方法的分析可以看讀源碼之模塊。 Stack 模塊為 Zepto 添加了 addSelf 和 end 方法。 讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡迎star: reading-zepto 源碼版本 本文閱讀的...
摘要:模塊是為解決移動版加載圖片過大過多時崩潰的問題。因為沒有處理過這樣的場景,所以這部分的代碼解釋不會太多,為了說明這個問題,我翻譯了這篇文章作為附文怎樣處理移動端對圖片資源的限制,更詳細地解釋了這個模塊的應(yīng)用場景。 assets 模塊是為解決 Safari 移動版加載圖片過大過多時崩潰的問題。因為沒有處理過這樣的場景,所以這部分的代碼解釋不會太多,為了說明這個問題,我翻譯了《How to...
摘要:模塊基于上的事件的封裝,利用屬性,封裝出系列事件。這個判斷需要引入設(shè)備偵測模塊。然后是監(jiān)測事件,根據(jù)這三個事件,可以組合出和事件。其中變量對象和模塊中的對象的作用差不多,可以先看看讀源碼之模塊對模塊的分析。 Gesture 模塊基于 IOS 上的 Gesture 事件的封裝,利用 scale 屬性,封裝出 pinch 系列事件。 讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡...
摘要:模塊處理的是表單提交。表單提交包含兩部分,一部分是格式化表單數(shù)據(jù),另一部分是觸發(fā)事件,提交表單。最終返回的結(jié)果是一個數(shù)組,每個數(shù)組項為包含和屬性的對象。否則手動綁定事件,如果沒有阻止瀏覽器的默認事件,則在第一個表單上觸發(fā),提交表單。 Form 模塊處理的是表單提交。表單提交包含兩部分,一部分是格式化表單數(shù)據(jù),另一部分是觸發(fā) submit 事件,提交表單。 讀 Zepto 源碼系列文章已...
閱讀 3939·2021-10-09 09:43
閱讀 2871·2021-10-08 10:05
閱讀 2734·2021-09-08 10:44
閱讀 882·2019-08-30 15:52
閱讀 2809·2019-08-26 17:01
閱讀 3016·2019-08-26 13:54
閱讀 1650·2019-08-26 10:48
閱讀 807·2019-08-23 14:41