$.Callbacks 是在版本 1.7 中新加入的。它是一個多用途的回調函數列表對象,提供了一種強大的方法來管理回調函數隊列。
整個 $.Callbacks 的源碼不到 200 行,它是一個工廠函數,使用函數調用方式(非new,它不是一個類)創建對象,它有一個可選參數flags用來設置回調函數的行為。
$.Callbacks 是在 jQuery 內部使用,如為 $.ajax,$.Deferred 等組件提供基礎功能的函數。它也可以用在類似功能的一些組件中,如自己開發的插件。
$.Callbacks構造的對象(以callbacks示例)主要包括以下方法:
- callbacks.add
- callbacks.remove
- callbacks.has
- callbacks.empty
- callbacks.disable
- callbacks.fire
- callbacks.fireWith
- callbacks.fired
- callbacks.lock
- callbacks.locked
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]);
傳數組進去實際在add內部判斷如果是數組會遞歸調用私有的add函數。此外,需注意add方法默認不去重,比如這里fn1添加兩次,fire時會觸發兩次。私有add方法有趣,它使用了具名函數立即執行其名僅在函數內可用。如圖中所圈之處
2. callbacks.remove 從回調隊列中刪除一個函數
function fn1() { console.log(1) } function fn2() { console.log(2) } var callbacks = $.Callbacks(); callbacks.add(fn1, fn2); callbacks.remove(fn1);
此時fire只會觸發fn2了。
此外remove方法會把添加多次的函數如fn1,全部刪除掉。為此還和cmc和nick討論為啥remove內部不用if而用while(開始我以為是jQuery寫錯了)。如下
var callbacks = $.Callbacks(); callbacks.add(fn1, fn2, fn1, fn2); callbacks.remove(fn1);
此時會把add兩次的fn1都刪掉,fire時只觸發fn2兩次。換成if則只刪fn1一次。
3. callbacks.has 判斷是否添加過某回調函數,不想重復添加時很有用
function fn1() { console.log(1) } var callbacks = $.Callbacks(); callbacks.add(fn1); if (!callbacks.has(fn1)) { callbacks.add(fn1); }
function fn1() { console.log(1) } function fn2() { console.log(2) } var callbacks = $.Callbacks(); callbacks.add(fn1); callbacks.add(fn2); callbacks.empty();
此時再fire不會觸發任何函數。empty函數實現很簡單,只是把內部的隊列管理對象list重置為一個空數組。這里可以了解清空數組的幾種方式。
function fn1() { console.log(1) } function fn2() { console.log(2) } var callbacks = $.Callbacks(); callbacks.disable(); callbacks.add(fn1); // 不起作用 callbacks.add(fn2); // 不起作用 callback.remove(fn1); // 不起作用 callbacks.fire(); // 不起作用
調用后再使用add, remove, fire等方法均不起作用。該方法內不是實際是將隊列管理對象list、stack、memory都置undefined了。
function fn() { console.log(this); // 上下文是callbacks console.log(arguments); // [3] } var callbacks = $.Callbacks(); callbacks.add(fn); callback.fire(3);
前面已經提到了,fire方法用來觸發回調函數,默認的上下文是callbacks對象,還可以傳參給回調函數。
7. callbacks.fireWith 同fire,但可以指定執行上下文
function fn() { console.log(this); // 上下文是person console.log(arguments); // [3] } var person = {name: 'jack'}; var callbacks = $.Callbacks(); callbacks.add(fn); callback.fireWith(person, 3);
其實fire內部調用的是fireWith,只是將上下文指定為this了,而this正是$.Callbacks構造的對象。
8. callbacks.fired 判斷是否有主動觸發過(調用fire或fireWith方法)
function fn1() { console.log(1) } var callbacks = $.Callbacks(); callbacks.add(fn1); callbacks.fired(); // false callbacks.fire(); callbacks.fired(); // true
注意,只要調用過一次fire或fireWith就會返回true。
$.Callbacks構造時可配置的參數Flags是可選的,字符串類型以空格分隔,有如下
function fn() { console.log(1) } var callbacks = $.Callbacks('once'); callbacks.add(fn); callbacks.fire(); // 打印1 callbacks.fire(); // fn不再觸發
function fn1() { console.log(1) } function fn2() { console.log(2) } var callbacks = $.Callbacks('memory'); callbacks.add(fn1); callbacks.fire(); // 必須先fire callbacks.add(fn2); // 此時會立即觸發fn2
memory有點繞,本意是記憶的意思。實際它的用法有點詭異,需結合特定場景來看(如jQuery.Deferred)。當首次調用fire后,之后每次add都會立即觸發。比如先callbacks.fire(),再callbacks.add(fn1),這時fn1會立即被調用。
如果是批量添加的,也都會被觸發
function fn1() { console.log(1) } function fn2() { console.log(2) } function fn3() { console.log(3) } var callbacks = $.Callbacks('memory'); callbacks.add(fn1); callbacks.fire(); callbacks.add([fn2, fn3]); // output 2, 3
function fn1() { console.log(1) } function fn2() { console.log(2) } var callbacks = $.Callbacks('unique'); callbacks.add(fn1); callbacks.add([fn1, fn2]); // 再次添加fn1 callbacks.fire(); // output 1, 2
這個很好理解,之前用has判斷去重,使用unique屬性則更方便。本例先add一次fn1,第二次再add時內部則會去重。因此最后fire時只輸出“1,2”而不是“1,1,2”。
4. stopOnFalse 回調函數返回false時中斷回調隊列的迭代
function fn1() { console.log(1) } function fn2() { console.log(2) return false // 注意這里 } function fn3() { console.log(3) } var callbacks = $.Callbacks('stopOnFalse'); callbacks.add(fn1, fn2, fn3); callbacks.fire(); // output 1,2
從該屬性名就能知了它的意圖,即回調函數通過return false來停止后續的回調執行。該示例添加了3個回調,fn2中使用return false,當fire執行到fn2時會停止執行,后續的fn3就不會被調用了。
用$.Callbacks實現觀察者模式
// 觀察者模式 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)
注:閱讀版本為1.8.3