Callbacks 子產品并不是必備的子產品,其作用是管理回調函數,為 Defferred 子產品提供支援,Defferred 子產品又為 Ajax 子產品的 <code>promise</code> 風格提供支援,接下來很快就會分析到 Ajax子產品,在此之前,先看 Callbacks 子產品和 Defferred 子產品的實作。
本文閱讀的源碼為 zepto1.2.0
将 Callbacks 子產品的代碼精簡後,得到的結構如下:
其實就是向 <code>zepto</code> 對象上,添加了一個 <code>Callbacks</code> 函數,這個是一個工廠函數,調用這個函數傳回的是一個對象,對象内部包含了一系列的方法。
<code>options</code> 參數為一個對象,在源碼的内部,作者已經注釋了各個鍵值的含義。
<code>options</code> : 構造函數的配置,預設為空對象
<code>list</code> : 回調函數清單
<code>stack</code> : 清單可以重複觸發時,用來緩存觸發過程中未執行的任務參數,如果清單隻能觸發一次,<code>stack</code> 永遠為 <code>false</code>
<code>memory</code> : 記憶模式下,會記住上一次觸發的上下文及參數
<code>fired</code> : 回調函數清單已經觸發過
<code>firing</code> : 回調函數清單正在觸發
<code>firingStart</code> : 回調任務的開始位置
<code>firingIndex</code> : 目前回調任務的索引
<code>firingLength</code>:回調任務的長度
我用 <code>jQuery</code> 和 <code>Zepto</code> 的時間比較短,之前也沒有直接用過 <code>Callbacks</code> 子產品,單純看代碼不易了解它是怎樣工作的,在分析之前,先看一下簡單的 <code>API</code> 調用,可能會有助于了解。
上面的例子隻是簡單的調用,也有了注釋,下面開始分析 <code>API</code>
<code>Callbacks</code> 子產品隻有一個内部方法 <code>fire</code> ,用來觸發 <code>list</code> 中的回調執行,這個方法是 <code>Callbacks</code> 子產品的核心。
<code>fire</code> 隻接收一個參數 <code>data</code> ,這個内部方法 <code>fire</code> 跟我們調用 <code>API</code> 所接收的參數不太一樣,這個 <code>data</code>是一個數組,數組裡面隻有兩項,第一項是上下文對象,第二項是回調函數的參數數組。
如果 <code>options.memory</code> 為 <code>true</code> ,則将 <code>data</code>,也即上下文對象和參數儲存下來。
将 <code>list</code> 是否已經觸發過的狀态 <code>fired</code> 設定為 <code>true</code>。
将目前回調任務的索引值 <code>firingIndex</code> 指向回調任務的開始位置 <code>firingStart</code> 或者回調清單的開始位置。
将回調清單的開始位置 <code>firingStart</code> 設定為回調清單的開始位置。
将回調任務的長度 <code>firingLength</code> 設定為回調清單的長度。
将回調的開始狀态 <code>firing</code> 設定為 <code>true</code>
執行回調的整體邏輯是周遊回調清單,逐個執行回調。
循環的條件是,清單存在,并且目前回調任務的索引值 <code>firingIndex</code> 要比回調任務的長度要小,這個很容易了解,目前的索引值都超出了任務的長度,就找不到任務執行了。
<code>list[firingIndex].apply(data[0], data[1])</code> 就是從回調清單中找到對應的任務,綁定上下文對象,和傳入對應的參數,執行任務。
如果回調執行後顯式傳回 <code>false</code>, 并且 <code>options.stopOnFalse</code> 設定為 <code>true</code> ,則中止後續任務的執行,并且清空 <code>memory</code> 的緩存。
回調任務執行完畢後,将 <code>firing</code> 設定為 <code>false</code>,表示目前沒有正在執行的任務。
清單任務執行完畢後,先檢查 <code>stack</code> 中是否有沒有執行的任務,如果有,則将任務參數取出,調用 <code>fire</code> 函數執行。後面會看到,<code>stack</code> 儲存的任務是 <code>push</code> 進去的,用 <code>shift</code> 取出,表明任務執行的順序是先進先出。
<code>memory</code> 存在,則清空回調清單,用 <code>list.length = 0</code> 是清空清單的一個方法。在全局參數中,可以看到, <code>stack</code> 為 <code>false</code> ,隻有一種情況,就是 <code>options.once</code> 為 <code>true</code> 的時候,表示任務隻能執行一次,是以要将清單清空。而 <code>memory</code> 為 <code>true</code> ,表示後面添加的任務還可以執行,是以還必須保持 <code>list</code> 容器的存在,以便後續任務的添加和執行。
其他情況直接調用 <code>Callbacks.disable()</code> 方法,禁用所有回調任務的添加和執行。
<code>start</code> 為原來回調清單的長度。儲存起來,是為了後面修正回調任務的開始位置時用。
<code>add</code> 方法的作用是将回調函數 <code>push</code> 進回調清單中。參數 <code>arguments</code> 為數組或者僞數組。
用 <code>$.each</code> 方法來周遊 <code>args</code> ,得到數組項 <code>arg</code>,如果 <code>arg</code> 為 <code>function</code> 類型,則進行下一個判斷。
在下一個判斷中,如果 <code>options.unique</code> 不為 <code>true</code> ,即允許重複的回調函數,或者原來的清單中不存在該回調函數,則将回調函數存入回調清單中。
如果 <code>arg</code> 為數組或僞數組(通過 <code>arg.length</code> 是否存在判斷,并且排除掉 <code>string</code> 的情況),再次調用 <code>add</code>函數分解。
調用 <code>add</code> 方法,向清單中添加回調函數。
如果回調任務正在執行中,則修正回調任務的長度 <code>firingLength</code> 為目前任務清單的長度,以便後續添加的回調函數可以執行。
否則,如果為 <code>memory</code> 模式,則将執行回調任務的開始位置設定為 <code>start</code> ,即原來清單的最後一位的下一位,也就是新添加進清單的第一位,然後調用 <code>fire</code> ,以緩存的上下文及參數 <code>memory</code> 作為 <code>fire</code> 的參數,立即執行新添加的回調函數。
删除清單中指定的回調。
用 <code>each</code> 周遊參數清單,在 <code>each</code> 周遊裡再有一層 <code>while</code> 循環,循環的終止條件如下:
<code>$.inArray()</code> 最終傳回的是數組項在數組中的索引值,如果不在數組中,則傳回 <code>-1</code>,是以這個判斷是确定回調函數存在于清單中。關于 <code>$.inArray</code> 的分析,見《讀zepto源碼之工具函數》。
然後調用 <code>splice</code> 删除 <code>list</code> 中對應索引值的數組項,用 <code>while</code> 循環是確定清單中有重複的回調函數都會被删除掉。
如果回調任務正在執行中,因為回調清單的長度已經有了變化,需要修正回調任務的控制參數。
如果 <code>index <= firingLength</code> ,即回調函數在目前的回調任務中,将回調任務數減少 <code>1</code> 。
如果 <code>index <= firingIndex</code> ,即在正在執行的回調函數前,将正在執行函數的索引值減少 <code>1</code> 。
這樣做是防止回調函數執行到最後時,沒有找到對應的任務執行。
以指定回調函數的上下文的方式來觸發回調函數。
<code>fireWith</code> 接收兩個參數,第一個參數 <code>context</code> 為上下文對象,第二個 <code>args</code> 為參數清單。
<code>fireWith</code> 後續執行的條件是清單存在并且回調清單沒有執行過或者 <code>stack</code> 存在(可為空數組),這個要注意,後面講 <code>disable</code> 方法和 <code>lock</code> 方法差別的時候,這是一個很重要的判斷條件。
先将 <code>args</code> 不存在時,初始化為數組。
再重新組合成新的變量 <code>args</code> ,這個變量的第一項為上下文對象 <code>context</code> ,第二項為參數清單,調用 <code>args.slice</code> 是對數組進行拷貝,因為 <code>memory</code> 會儲存上一次執行的上下文對象及參數,應該是怕外部對引用的更改的影響。
如果回調正處在觸發的狀态,則将上下文對象和參數先儲存在 <code>stack</code> 中,從内部函數 <code>fire</code> 的分析中可以得知,回調函數執行完畢後,會從 <code>stack</code> 中将 <code>args</code> 取出,再觸發 <code>fire</code> 。
否則,觸發 <code>fire</code>,執行回調函數清單中的回調函數。
<code>add</code> 和 <code>remove</code> 都要判斷 <code>firing</code> 的狀态,來修正回調任務控制變量,<code>fire</code> 方法也要判斷 <code>firing</code> ,來判斷是否需要将 <code>args</code> 存入 <code>stack</code> 中,但是 <code>javascript</code> 是單線程的,照理應該不會出現在觸發的同時 <code>add</code>或者 <code>remove</code> 或者再調用 <code>fire</code> 的情況。
<code>fire</code> 方法,用得最多,但是卻非常簡單,調用的是 <code>fireWidth</code> 方法,上下文對象是 <code>this</code> 。
<code>has</code> 有兩個作用,如果有傳參時,用來查測所傳入的 <code>fn</code> 是否存在于回調清單中,如果沒有傳參時,用來檢測回調清單中是否已經有了回調函數。
這個三元表達式前面的是判斷指定的 <code>fn</code> 是否存在于回調函數清單中,後面的,如果 <code>list.length</code> 大于 <code>0</code> ,則回調清單已經存入了回調函數。
<code>empty</code> 的作用是清空回調函數清單和正在執行的任務,但是 <code>list</code> 還存在,還可以向 <code>list</code> 中繼續添加回調函數。
<code>disable</code> 是禁用回調函數,實質是将回調函數清單置為 <code>undefined</code> ,同時也将 <code>stack</code> 和 <code>memory</code> 置為 <code>undefined</code> ,調用 <code>disable</code> 後,<code>add</code> 、<code>remove</code> 、<code>fire</code> 、<code>fireWith</code> 等方法不再生效,這些方法的首要條件是 <code>list</code> 存在。
回調是否已經被禁止,其實就是檢測 <code>list</code> 是否存在。
鎖定回調清單,其實是禁止 <code>fire</code> 和 <code>fireWith</code> 的執行。
其實是将 <code>stack</code> 設定為 <code>undefined</code> , <code>memory</code> 不存在時,調用的是 <code>disable</code> 方法,将整個清單清空。效果等同于禁用回調函數。<code>fire</code> 和 <code>add</code> 方法都不能再執行。
為什麼 <code>memory</code> 存在時,<code>stack</code> 為 <code>undefined</code> 就可以将清單的 <code>fire</code> 和 <code>fireWith</code> 禁用掉呢?在上文的 <code>fireWith</code> 中,我特别提到了 <code>!fired || stack</code> 這個判斷條件。在 <code>stack</code> 為 <code>undefined</code> 時,<code>fireWith</code> 的執行條件看 <code>fired</code> 這個條件。如果回調清單已經執行過, <code>fired</code> 為 <code>true</code> ,<code>fireWith</code> 不會再執行。如果回調清單沒有執行過,<code>memory</code> 為 <code>undefined</code> ,會調用 <code>disable</code> 方法禁用清單,<code>fireWith</code>也不能執行。
是以,<code>disable</code> 和 <code>lock</code> 的差別主要是在 <code>memory</code> 模式下,回調函數觸發過後,<code>lock</code> 還可以調用 <code>add</code>方法,向回調清單中添加回調函數,添加完畢後會立刻用 <code>memory</code> 的上下文和參數觸發回調函數。
回調清單是否被鎖定。
其實就是檢測 <code>stack</code> 是否存在。
回調清單是否已經被觸發過。
回調清單觸發一次後 <code>fired</code> 就會變為 <code>true</code>,用 <code>!!</code> 的目的是将 <code>undefined</code> 轉換為 <code>false</code> 傳回。
本文轉自 sshpp 51CTO部落格,原文連結:http://blog.51cto.com/12902932/1950348,如需轉載請自行聯系原作者