$.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
