jQuery源碼分析之Callbacks詳解
代碼的本質(zhì)突出順序、有序這一概念,尤其在javascript——畢竟javascript是單線程引擎。
javascript擁有函數(shù)式編程的特性,而又因?yàn)閖avascript單線程引擎,我們的函數(shù)總是需要有序的執(zhí)行。優(yōu)秀代碼常常 把函數(shù)切割成各自的模塊,然后在某一特定條件下執(zhí)行,既然這些函數(shù)是有序的執(zhí)行,那么我們?yōu)槭裁床痪帉懸粋€(gè)統(tǒng)一管理的對(duì)象,來(lái)幫助我們管理這些函數(shù)——于是,Callbacks(回調(diào)函數(shù))誕生。
什么是Callbacks
javascript中充斥著函數(shù)編程,例如最簡(jiǎn)單的window.onload承接的就是一個(gè)函數(shù),悲催的是window.onload直接賦值的話只能接收一個(gè)函數(shù),如果有好幾個(gè)函數(shù)想要在onload中執(zhí)行,那么我們就需要編寫如下代碼:
function a(elem) {
elem.innerHTML = '我是函數(shù)a,我要改變Element的HTML結(jié)構(gòu)';
};
function b(elem) {
elem.innerHTML = '我的函數(shù)b,我要改變Element的style';
}
window.onload = function () {
var elem = document.getElementById('test');
a(elem);
b(elem);
};
回調(diào)函數(shù)初衷就是建立在這么個(gè)玩意兒的上面,不再讓我們分散這些函數(shù),而是把這些函數(shù)統(tǒng)一整理。可以看見,我們?cè)趙indow.onload中希望針對(duì)一個(gè)Element做兩件事情:先改變html結(jié)構(gòu),然后改變這個(gè)html的style。兩個(gè)函數(shù)同樣是針對(duì)一個(gè)Element操作,而這兩個(gè)函數(shù)最終的執(zhí)行都是有序進(jìn)行的。那么我們?yōu)槭裁床痪帉懸粋€(gè)這樣的對(duì)象管理這些函數(shù)呢。當(dāng)然, 這只是回調(diào)函數(shù)的最基礎(chǔ)的存在意義,我們需要的不僅僅是這樣一個(gè)簡(jiǎn)單的回調(diào)函數(shù)對(duì)象,我們需要一個(gè)更加強(qiáng)大的回調(diào)函數(shù)。好吧,這只是一個(gè)簡(jiǎn)單的用例,那么我可以告訴你這個(gè)回調(diào)函數(shù)除了一個(gè)個(gè)執(zhí)行函數(shù)之外,它還可以做什么。
Callbacks本質(zhì)就是控制函數(shù)有序的執(zhí)行,Javascript是單線程引擎,也就說(shuō),javascript同一時(shí)間只會(huì)有一處代碼在運(yùn)行——即便是Ajax、setTimeout。 這兩個(gè)函數(shù)看起來(lái)好像都是異步的,其實(shí)并非如此,瀏覽器在運(yùn)行javascript代碼的時(shí)候,這些代碼都會(huì)被有序的壓入一個(gè)隊(duì)列中,當(dāng)你運(yùn)行Ajax的時(shí)候,瀏覽器會(huì)把Ajax 壓入代碼隊(duì)列,瀏覽器在處理javascript代碼是從這個(gè)代碼隊(duì)列中一個(gè)一個(gè)取代碼執(zhí)行的——Callbacks,迎合了這種單線程引擎。
當(dāng)然,我們要的,不僅僅是這樣一個(gè)簡(jiǎn)單的工具對(duì)象——在jQuery源碼中,Callbacks提供了一組函數(shù)的基本管理,為Deferred(異步隊(duì)列)提供了基礎(chǔ),同時(shí)也服務(wù)于Queue(同步隊(duì)列)。 Deferred用于抹平/扁平化金字塔編程(大量的回調(diào)函數(shù)嵌套,例如Ajax中需要根據(jù)請(qǐng)求返回碼決定執(zhí)行的代碼); 而Queue,驅(qū)動(dòng)著jQuery.animate(動(dòng)畫引擎)。
那么我們就來(lái)編寫一個(gè)Callbacks吧。
Callbacks模型
Array(數(shù)組):
既然我們Callbacks要承接一系列函數(shù),那么必然需要有一個(gè)容器。我們可以使用一個(gè)數(shù)組,并把每一個(gè)函數(shù)壓到該數(shù)組中,需要執(zhí)行的時(shí)候,循環(huán)數(shù)組項(xiàng)執(zhí)行。
工作模型:
這個(gè)Callbacks需要非常的強(qiáng)大,并不僅僅是壓入函數(shù),然后執(zhí)行這么簡(jiǎn)單,這個(gè)Callbacks應(yīng)該擁有良好的執(zhí)行模型。
once:當(dāng)前Callbacks對(duì)象中所有的函數(shù)只會(huì)執(zhí)行一次,執(zhí)行一次完之后就會(huì)被釋放掉,我們可以為使用Callbacks對(duì)象的用戶提供一個(gè)穩(wěn)定有效的方案,確保函數(shù)只會(huì)執(zhí)行一次,之后不再執(zhí)行,穩(wěn)定了這些函數(shù)的線程。
auto:自動(dòng)執(zhí)行模型,這是個(gè)有意思的模型,有些函數(shù)依賴上一層函數(shù),例如函數(shù)b的執(zhí)行依賴于函數(shù)a,那么我們提供一個(gè)自動(dòng)執(zhí)行的模型:第一次執(zhí)行這個(gè)Callbacks之后,每次添加函數(shù)到Callbacks的時(shí)候,自動(dòng)執(zhí)行過(guò)去添加的那些函數(shù),并把最后一次給定的參數(shù)數(shù)據(jù)傳遞給過(guò)去的那些函數(shù),這樣就從Callbacks中抹平了這些依賴函數(shù)之間需要反復(fù)觸發(fā)的關(guān)系,這是個(gè)有意思的模型。
once&auto:我們可以讓它更強(qiáng)大,同時(shí)工作once和auto模型,即:當(dāng)每次添加函數(shù)到Callbacks中的時(shí)候,過(guò)去的函數(shù)都會(huì)執(zhí)行,然后,釋放掉這些過(guò)去的函數(shù),下次繼續(xù)添加函數(shù)的時(shí)候,過(guò)去的那些函數(shù)不會(huì)再執(zhí)行,因?yàn)閛nce模型,已經(jīng)把它們釋放掉了。
API:
add(function) - 添加一個(gè)(或多個(gè))函數(shù)到Callbacks對(duì)象中:當(dāng)然,如果你并不添加函數(shù)只是好奇看看Callbacks,我們也將讓你繼續(xù)享受你的樂(lè)趣——我們并不會(huì)拋出異常,因?yàn)檫@對(duì)于我們來(lái)說(shuō)并不擅長(zhǎng)。
remove(function) - 移除一個(gè)Callbacks中的一個(gè)函數(shù):既然有了添加,那么我們也應(yīng)該提供反悔的方案,我們是多么的平易近人,容忍著別人過(guò)去所做的一切。
has(function) - 判斷Callbacks中是否包含一個(gè)函數(shù):哦?你竟然不確定是否包含這個(gè)函數(shù),當(dāng)初可是你丟進(jìn)來(lái)的??!你怎么如此馬虎?不過(guò)既然你問(wèn)我的話,我仍然會(huì)告訴你Callbacks是否包含這個(gè)函數(shù),我知道你很繁忙,并不能記住和確定所有的事情。
empty() - 清空Callbacks:這些函數(shù)對(duì)于你失去了意義了么?什么?已經(jīng)執(zhí)行過(guò)你就不想要了?所以你希望可以清空它?好吧,為了內(nèi)存君我還是忍下你這個(gè)需求。
disable() - 廢掉一個(gè)Callbacks:為了和別人的代碼穩(wěn)定的存在,我選擇了自我犧牲——沒錯(cuò),這個(gè)方法可以廢掉Callbacks,徹底的廢掉,就如同它曾經(jīng)尚未存在過(guò)一般。
disabled() - 判斷這個(gè)Callbacks是否已經(jīng)被廢掉:如果你仍然不相信Callbacks是否真的自我犧牲,那么這個(gè)方法可以讓你安心。
lock(boolean) - 鎖定這個(gè)Callbacks對(duì)象:你害怕它并不穩(wěn)定,但是你又不想舍棄它,lock是個(gè)不錯(cuò)的方法,它接收一個(gè)Boolean的參數(shù),表示是否需要鎖定這個(gè)對(duì)象,當(dāng)然,無(wú)參的它用于讓你確定Callbacks是否被鎖定。
fire(data) - 執(zhí)行這個(gè)Callbacks中的函數(shù):我們做的這一切,不都是為了這一刻執(zhí)行的宿命么?參數(shù)將會(huì)成為這些需要執(zhí)行的函數(shù)的參數(shù)。
fireWith(context,data) - 執(zhí)行Callbacks中的函數(shù),并且指定上下文。在fire()里,所有的函數(shù)的Context(上下文)都是Callbacks對(duì)象,而fireWidth(),可以讓你重新定義這些要執(zhí)行的函數(shù)的上下文,多么自由的編程啊,Callbacks為你考慮了一切。
fired() - 判斷這個(gè)Callbacks過(guò)去是否已經(jīng)執(zhí)行過(guò):我們相信,多數(shù)時(shí)候你并不知道過(guò)去做過(guò)什么,但是我們記錄了你做的一切,如果你過(guò)去曾經(jīng)執(zhí)行過(guò)這個(gè)Callbacks對(duì)象,那么你休想否認(rèn),因?yàn)槲覀冎肋^(guò)去你是否執(zhí)行了這個(gè)Callbacks。
基本模塊實(shí)現(xiàn)
簡(jiǎn)單的實(shí)現(xiàn):
我們先來(lái)簡(jiǎn)單的實(shí)現(xiàn)一個(gè)Callbacks:
(function (window, undefined) {
var Callbacks = function () {
//通過(guò)閉包保護(hù)這些私有變量
var list = [],//回調(diào)函數(shù)列表
fired;//是否執(zhí)行過(guò)
//返回一個(gè)閉包的Callbakcs對(duì)象
return {
add: function (fn) {
//當(dāng)Callbacks廢棄掉的時(shí)候,list為undefined
if (list) {
//添加一個(gè)回調(diào)函數(shù)
list.push(fn);
//支持鏈?zhǔn)交卣{(diào)
}
return this;
},
fireWith: function (context, data) {
//觸發(fā)回調(diào)函數(shù),并指定上下文
if (list) {
fired = true;
for (var i = 0, len = list.length; i < len; i++) {
//當(dāng)Callbacks中某一個(gè)函數(shù)返回false的時(shí)候,停止Callbacks后續(xù)的執(zhí)行
if (list[i].apply(context, data) === false)
break;
}
}
return this;
},
fire: function () {
//觸發(fā)回調(diào)函數(shù)
//調(diào)用fireWith并指定上下文
return this.fireWith(this, arguments);
},
empty: function () {
//清空l(shuí)ist即可
if (list)//當(dāng)這個(gè)Callbacks廢棄掉的時(shí)候,Callbacks不應(yīng)該可以繼續(xù)使用
list = [];
return this;
},
disable: function () {
//廢棄這個(gè)Callbacks對(duì)象,后續(xù)的回調(diào)函數(shù)列表不再執(zhí)行
list = undefined;
return this;
},
disabled: function () {//檢測(cè)這個(gè)Callbacks是否已經(jīng)廢掉
//轉(zhuǎn)換為boolean返回
return !list;
},
fired: function () {//這個(gè)callbacks是否執(zhí)行過(guò)
return !!fired;
}
};
};
//注冊(cè)到window下
window.Callbacks = Callbacks;
}(window));
然后我們測(cè)試一下這個(gè)Callbacks:
var test = new Callbacks();
test.add(function (value) {
console.log('函數(shù)1,value是:' + value);
});
test.add(function (value) {
console.log('函數(shù)2,value是:' + value);
});
test.fire('這是函數(shù)1和函數(shù)2的值');
console.log('查看函數(shù)是否執(zhí)行過(guò):' + test.fired());
test.disable();//廢棄這個(gè)Callbacks
console.log('查看函數(shù)是否被廢棄:' + test.disabled());
test.add(function () {
console.log('添加第三個(gè)函數(shù),這個(gè)函數(shù)不應(yīng)該被執(zhí)行');
});
test.fire();
打開瀏覽器的控制臺(tái)我們可以看見運(yùn)行結(jié)果正常。
once和auto(memory)實(shí)現(xiàn)
once:
once讓這個(gè)callbacks中的函數(shù)運(yùn)行一次之后就不再運(yùn)行。原理非常的簡(jiǎn)單,上面的代碼中,我們可以看見有一個(gè)變量list承接函數(shù)列表,所以我們只需要把過(guò)去執(zhí)行過(guò)的代碼清空即可。我們用一個(gè)全局變量,保存當(dāng)前執(zhí)行模型,如果是once模型,就在fireWith()里讓這個(gè)list失效即可:
(function (window, undefined) {
var Callbacks = function (once) {
//通過(guò)閉包保護(hù)這些私有變量
var list = [],//回調(diào)函數(shù)列表
fired;//是否執(zhí)行過(guò)
//返回一個(gè)閉包的Callbakcs對(duì)象
return {
//...省略部分代碼
fireWith: function (context, data) {
//觸發(fā)回調(diào)函數(shù),并指定上下文
if (list) {
fired = true;
for (var i = 0, len = list.length; i < len; i++) {
//當(dāng)Callbacks中某一個(gè)函數(shù)返回false的時(shí)候,停止Callbacks后續(xù)的執(zhí)行
if (list[i].apply(context, data) === false)
break;
}
}
//如果配置了once模型,則全局變量once為true,則list重置
if (once) list = undefined;
return this;
}
//...省略部分代碼
};
};
//注冊(cè)到window下
window.Callbacks = Callbacks;
}(window));
auto:
auto(memory)模型在jQuery中是以memory命名的,最初被這個(gè)命名給混淆了,仔細(xì)看了用法才確定改成auto——它的作用就是“第一次fire()之后,后續(xù)add()的函數(shù)自動(dòng)執(zhí)行”,以下情況可以用到:當(dāng)添加一組函數(shù)到Callbacks之后,臨時(shí)又需要追加一個(gè)函數(shù),那么即時(shí)運(yùn)行這個(gè)新追加的函數(shù)——不得不說(shuō),為了使用的便利,這個(gè)模式變得有點(diǎn)難以理解。實(shí)現(xiàn)起來(lái)就是在add()的時(shí)候判斷是否是auto模型,如果是auto模型,則執(zhí)行這個(gè)函數(shù)。 但是,我們需要在第一次fire()之后才自動(dòng)執(zhí)行,沒有fire()過(guò)的Callbacks并不該被自動(dòng)執(zhí)行,并且,每次自動(dòng)執(zhí)行后,還需要把最后一次使用的參數(shù)傳遞傳遞給這個(gè)自動(dòng)執(zhí)行的函數(shù)。
或許大家會(huì)想到如下代碼:
(function (window, undefined) {
var Callbacks = function (once, auto) {
var list = [],
fired,
lastData;//保存最后一次執(zhí)行的參數(shù)
return {
add: function (fn) {
if (list) {
list.push(fn);
// — 自動(dòng)執(zhí)行模式
//最后一次使用的參數(shù)傳遞過(guò)去,這里丟失了Context(上下文)
//為了不讓這里丟失上下文,我們或許還需要聲明一個(gè)變量保存最后一次使用的Context
if (auto) this.fire(lastData);
}
return this;
},
fireWith: function (context, data) {
if (list) {
lastData = data;// — 記錄最后一次使用的參數(shù)
fired = true;
for (var i = 0, len = list.length; i < len; i++) {
if (list[i].apply(context, data) === false)
break;
}
}
if (once) list = [];
return this;
}
//部分代碼省略
};
};
//注冊(cè)到window下
window.Callbacks = Callbacks;
}(window));
但是在jQuery里采用了更奇妙的用法,獲取jQuery作者也自豪這種用法,所以命名這個(gè)模型為memory——就是讓上面的變量auto不僅僅表示當(dāng)前是auto執(zhí)行模式,并且作為最后一次參數(shù)的容器,它既表示了auto,也表示了memory。(下面的代碼非jQuery是根據(jù)jQuery代碼思路而寫,非源碼):
(function (window, undefined) {
var Callbacks = function (auto) {
var list = [],
fired,
memory,//主演在這里,就是memory
coreFire = function (data) {
//真正的觸發(fā)函數(shù)方法
if (list) {
//&&表達(dá)式妙用
memory = auto && data;//記錄最后一次的參數(shù),如果不是auto模式則不會(huì)記錄這個(gè)參數(shù)
//如果是auto模式,那么這個(gè)auto將不會(huì)為false,它會(huì)是一個(gè)數(shù)組
fired = true;
for (var i = 0, len = list.length; i < len; i++) {
if (list[i].apply(data[0], data[1]) === false)
break;
}
}
};
return {
add: function (fn) {
if (list) {
//添加一個(gè)回調(diào)函數(shù)
list.push(fn);
//自動(dòng)執(zhí)行模式,注意如果auto模型
//memory是在coreFire()里賦值的,默認(rèn)是false
if (memory) coreFire(auto);
}
//支持鏈?zhǔn)交卣{(diào)
return this;
},
fireWith: function (context, data) {
if (once) list = [];
//這里調(diào)用coreFire,把參數(shù)轉(zhuǎn)換為數(shù)組了
coreFire([context, data]);
return this;
}
/*部分代碼省略*/
};
};
window.Callbacks = Callbacks;
}(window));
我們?cè)谏弦粋€(gè)auto實(shí)現(xiàn)的代碼中看到我們丟失了Context,jQuery早在fireWith()中修復(fù)了這個(gè)bug——在fireWith()中修復(fù)參數(shù)。jQuery把fireWith()中本來(lái)應(yīng)該執(zhí)行函數(shù)的邏輯給抽離出來(lái),我們暫時(shí)將它命名為coreFire(),在原fireWith()中,將參數(shù)拼接成一個(gè)數(shù)組:第一個(gè)參數(shù)表示上下文,第二個(gè)參數(shù)表示傳遞進(jìn)來(lái)的參數(shù)。然后執(zhí)行coreFire()。
在add()的時(shí)候,jQuery并沒有給變量auto(memory)賦值,而是選擇在coreFire()中給auto(memory)賦值,這樣就保證了第一次fire()之后才會(huì)開啟自動(dòng)執(zhí)行。
按照上面所說(shuō),coreFire()接收的參數(shù)其實(shí)是一個(gè)數(shù)組,第一個(gè)參數(shù)是上下文,第二個(gè)參數(shù)是外面?zhèn)鬟f進(jìn)來(lái)的參數(shù)。同時(shí)把這個(gè)數(shù)組賦值給auto(memory),這樣,變量auto(是否自動(dòng)執(zhí)行模式)的定義就變成了memory(記憶最后一次傳遞的參數(shù))。
真是一石二鳥的神思路,神想法,不得不點(diǎn)贊。我定義這個(gè)為auto是因?yàn)樗谋旧砭褪且粋€(gè)自動(dòng)執(zhí)行的模型,順便保存了最后一次fire()的參數(shù),而jQuery定義為memory或許也是作者感嘆這里的鬼斧神工吧。
至于once&auto就是把這兩個(gè)代碼揉合到一起而已,只需要在coreFire()里判定如果是auto模式,那么就把list重置為一個(gè)新的數(shù)組,否則直接設(shè)置為undefined即可。
源碼
這份代碼是自己對(duì)應(yīng)jQuery手寫的一份,將一些jQuery公有的函數(shù)都寫了進(jìn)來(lái),并非代碼片段,所以可以直接引用運(yùn)行。
(function (window, undefined) {
/*
* 一個(gè)回調(diào)函數(shù)工具對(duì)象,注意這個(gè)工作對(duì)象工作完成之后就會(huì)清空數(shù)組:
* 提供一組普通的API,但它有如下工作模型 -
* once - 單次執(zhí)行模型:每次工作一次,后續(xù)不再工作
* auto - 自動(dòng)執(zhí)行模型:每添加一個(gè)回調(diào)函數(shù),自動(dòng)執(zhí)行現(xiàn)有的回調(diào)函數(shù)集合里的所有回調(diào)函數(shù),并將本次的參數(shù)傳遞給所有的回調(diào)函數(shù)
*
*/
//工具函數(shù)
var isIndexOf = Array.prototype.indexOf, //Es6
toString = Object.prototype.toString, //緩存toString方法
toSlice = Array.prototype.slice, //緩存slice方法
isFunction = (function () { //判定一個(gè)對(duì)象是否是Function
return "object" === typeof document.getElementById ?
isFunction = function (fn) {
//ie下對(duì)DOM和BOM的識(shí)別有問(wèn)題
try {
return /^\s*\bfunction\b/.test("" + fn);
} catch (x) {
return false
}
} :
isFunction = function (fn) { return toString.call(fn) === '[object Function]'; };
})(),
each = function () { //循環(huán)遍歷方法
//第一個(gè)參數(shù)表示要循環(huán)的數(shù)組,第二個(gè)參數(shù)是每次循環(huán)執(zhí)行的函數(shù)
if (arguments.length < 2 || !isFunction(arguments[1])) return;
//為什么slice無(wú)效??
var list = toSlice.call(arguments[0]),
fn = arguments[1],
item;
while ((item = list.shift())) {//沒有直接判定length,加速
// 為什么這里用call就可以,而apply就不行?
//搞定 - apply的第二個(gè)參數(shù)必須是一個(gè)array對(duì)象(沒有驗(yàn)證array-like是否可以,而call沒有這個(gè)要求)
//apply是這樣描述的:如果 argArray(第二個(gè)參數(shù)) 不是一個(gè)有效的數(shù)組或者不是 arguments 對(duì)象,那么將導(dǎo)致一個(gè) TypeError。
fn.call(window, item);
}
},
inArray = function () { //檢測(cè)數(shù)組中是否包含某項(xiàng),返回該項(xiàng)索引
//預(yù)編譯
return isIndexOf ? function (array, elem, i) {
if (array)
return isIndexOf.call(array, elem, i);
return -1;
} : function (elem, array, i) {
var len;
if (array) {
len = array.length;
i = i ? i < 0 ? Math.max(0, len + i) : i : 0;
for (; i < len; i++) {
if (i in array && array[i] === elem) {
return i;
}
}
}
return -1;
}
}();
var Callbacks = function (option) {
option = toString.call(option) === '[object Object]' ? option : {};
//使用閉包,因?yàn)槊總€(gè)新建的callbacks都有自己的狀態(tài)
var list = [], //回調(diào)列表
_list = [], //如果鎖定這個(gè)callbacks對(duì)象,則清空l(shuí)ist,將原list置入_list
fired, //是否執(zhí)行過(guò)
firingStart, //當(dāng)前回調(diào)函數(shù)列表執(zhí)行的函數(shù)索引(起點(diǎn))
firingLength, //回調(diào)函數(shù)的數(shù)組長(zhǎng)度
auto, //標(biāo)志是否自動(dòng)執(zhí)行,如果需要自動(dòng)執(zhí)行,則auto記憶著最后一次回調(diào)的參數(shù)(最后一次fire的參數(shù)),這是一個(gè)很詭異的且奇葩的用法
//這個(gè)變量用法很詭異和犀利,既包含了是否指定執(zhí)行的標(biāo)志,又記錄了數(shù)據(jù)
//這個(gè)auto配合once簡(jiǎn)直就是喪心病狂:【第一次】執(zhí)行了fire后才會(huì)自動(dòng)執(zhí)行,配合once可以做到:一次執(zhí)行,后面不再追加和執(zhí)行代碼,保證了一組回調(diào)數(shù)據(jù)的穩(wěn)定和安全
stack = !option.once && [], //一個(gè)callbacks棧,如果當(dāng)前正在執(zhí)行回調(diào)數(shù)組,而在執(zhí)行中又新添了回調(diào)函數(shù),那么把新的回調(diào)函數(shù),那么新的回調(diào)函數(shù)都會(huì)壓入該棧
firing = false, //callbacks是否正在工作/執(zhí)行
//觸發(fā)回調(diào)函數(shù)
fire = function (data) {
//注意這個(gè)data是個(gè)數(shù)組,如果配置了auto模式,那么auto永遠(yuǎn)不會(huì)為false,因?yàn)閍uto會(huì)是個(gè)數(shù)組
auto = option.auto && data; //在這里,如果配置要求記憶最后的參數(shù),則記憶這個(gè)參數(shù)(非常犀利的用法,直接取了數(shù)據(jù))
fired = true;
firingIndex = firingStart || 0;
firingStart = 0;//清空f(shuō)iringStart(不清空下次執(zhí)行有出問(wèn)題啦)
firingLength = list.length; //緩存list長(zhǎng)度,外界可以訪問(wèn)
firing = true; //正在執(zhí)行回調(diào)函數(shù)
for (; firingIndex < firingLength; firingIndex++) {
if (list[firingIndex].apply(data[0], data[1]) === false) {
//注意,如果配置了option.auto(自動(dòng)執(zhí)行),并且stack(棧)里存在函數(shù),那么add()代碼里有一段對(duì)于auto判定會(huì)直接執(zhí)行本方法的代碼
//我們要阻止掉那段代碼,所以設(shè)置auto為false
auto = false;
break;
}//當(dāng)函數(shù)返回false,終止執(zhí)行后續(xù)隊(duì)列
}
firing = false; //標(biāo)志狀態(tài)已經(jīng)執(zhí)行完畢回調(diào)函數(shù)[stack(棧)里面的函數(shù)尚未執(zhí)行]
//如果這個(gè)棧在沒有配置once的情況下肯定是[],所以一定存在
//這里主要作用是,如果沒有配置once,則攔截下面的代碼,如果配置了once,執(zhí)行完代碼清空數(shù)據(jù)
if (stack) {
if (stack.length)//先把下面清空l(shuí)ist狀態(tài)的代碼攔截掉,再判定是否有棧
fire(stack.shift()); //從棧頭部取出,并遞歸fire()方法
}
else if (auto) //代碼走到這里,證明已經(jīng)配置了option.once(只執(zhí)行一次),于是把list清空
list = [];
else //證明沒有配置auto,但是配置了once,那么祭出終極大法,直接廢了這個(gè)callbacks對(duì)象
self.disable();
};
var self = {
add: function () {//添加一個(gè)回調(diào)函數(shù)
if (list) {
var start = list.length;
(function addCallback(args) {
each(args, function (item) {
if (isFunction(item)) {//是函數(shù),則壓入回調(diào)列表
list.push(item);
//注意typeof 和Object.prototype.toString是不一樣的
} else if (toString.call(item) === '[object Array]') {//如果是個(gè)數(shù)組,則遞歸壓入回調(diào)列表,這個(gè)判定拋棄了array-like
addCallback(item);
}
});
})(arguments);
}
if (firing)//如果當(dāng)前正有回調(diào)函數(shù)在執(zhí)行,那么需要更新當(dāng)前回調(diào)函數(shù)列表的length,否則這個(gè)新壓入的回調(diào)函數(shù)就會(huì)被掠過(guò)。
firingLength = list.length;
else if (auto) {//如果當(dāng)前沒有執(zhí)行回調(diào)函數(shù),并且要求自動(dòng)執(zhí)行
//注意這里是給firingStart賦值,上面fire方法中正在使用的是firingIndex,這里不會(huì)影響到上面代碼的執(zhí)行線路
firingStart = start;
//執(zhí)行我們新加入的小伙伴
fire(auto);
}
return this;
},
fire: function () {//觸發(fā)回調(diào)函數(shù)
self.fireWith(this, arguments);
return this;
},
fireWith: function (context, args) {//觸發(fā)回調(diào)函數(shù),并指定上下文
//如果配置了once,stack將為undefined,而once又需要保證只執(zhí)行一次,所以一旦執(zhí)行過(guò)一次,這里的代碼不會(huì)再執(zhí)行
if (list && (!fired || stack)) {
//修正參數(shù)
//在這里,context索引為0
//而參數(shù)列表索引為2
//轉(zhuǎn)換為數(shù)組訪問(wèn)是因?yàn)閷?duì)象表示更加的消耗資源,在頂層的fire()代碼中有auto[記憶參數(shù),自動(dòng)執(zhí)行]這個(gè)功能,如果采用對(duì)象則開銷了更大的內(nèi)存
args = [context,
args ?
args.slice && args.slice()
|| toSlice.call(args) :
[]
];
fire(args);
}
return this;
},
remove: function () {//移除一個(gè)回調(diào)函數(shù)
if (list) {
each(arguments, function (item) {
var index;
//可能有多項(xiàng),index可以在循環(huán)中表示檢索的范圍,之前檢索的過(guò)的可以不用再檢索
while ((index = inArray(item, list, index)) > -1) {
list.splice(index, 1);
if (firing) {
//保證上面fire中正在執(zhí)行的函數(shù)列表能夠正確運(yùn)行,fire中設(shè)定全局這些變量為的就是這里可以異步移除
if (index <= firingLength)//修正長(zhǎng)度
firingLength--;
if (index <= firingLength)//修正索引
firingIndex--;
}
}
});
}
return this;
},
has: function (fn) {//是否包含一個(gè)回調(diào)函數(shù)
return fn ? inArray(fn, list) > -1 : list && list.length;
},
empty: function () {//清空這個(gè)callbacks對(duì)象
list = [];
firingLength = 0;
return this;
},
disable: function () {//廢掉這個(gè)callbacks對(duì)象,后續(xù)的回調(diào)函數(shù)列表不再執(zhí)行
list = stack = auto = undefined;
return this;
},
disabled: function () {//是否已經(jīng)廢掉
return !list; //轉(zhuǎn)換為boolean
},
lock: function (isLock) {//鎖定或解鎖這個(gè)callbacks對(duì)象
//無(wú)參,判斷這個(gè)callbacks是否被鎖定
if (isLock == null) return !!_list;
if (isLock) {//鎖
_list = stack && list.concat(stack) || list;
list = undefined;
} else {//解鎖,jQuery并沒有提供解鎖功能,解鎖讓Callbacks變得不穩(wěn)定
list = _list;
_list = undefined;
}
return this;
},
fired: function () {//這個(gè)callbacks是否執(zhí)行過(guò)
//轉(zhuǎn)換為boolean,包括undefined,null,''等
return !!fired;
}
};
return self;
};
window.$ = window.$ || {};
window.$.Callbacks = window.Callbacks = Callbacks;
}(window));
下載
Github:https://github.com/linkFly6/linkfly.so/blob/master/LinkFLy/jQuery/jQuery.LinkFLy/Callbacks.js
以上就是本文給大家分享的全部?jī)?nèi)容了,希望大家能夠喜歡。
- 自己動(dòng)手實(shí)現(xiàn)jQuery Callbacks完整功能代碼詳解
- jQuery.Callbacks()回調(diào)函數(shù)隊(duì)列用法詳解
- jQuery回調(diào)函數(shù)的定義及用法實(shí)例
- 使用jQuery中的when實(shí)現(xiàn)多個(gè)AJAX請(qǐng)求對(duì)應(yīng)單個(gè)回調(diào)的例子分享
- jQuery Tips 為AJAX回調(diào)函數(shù)傳遞額外參數(shù)的方法
- Jquery版本導(dǎo)致Ajax不執(zhí)行success回調(diào)函數(shù)
- jQuery AJAX回調(diào)函數(shù)this指向問(wèn)題
- Jquery Post處理后不進(jìn)入回調(diào)的原因及解決方法
- 從零學(xué)jquery之如何使用回調(diào)函數(shù)
- jquery.Callbacks的實(shí)現(xiàn)詳解
相關(guān)文章
etmvc+jQuery EasyUI+combobox多值操作實(shí)現(xiàn)角色授權(quán)實(shí)例
本篇文章主要介紹了etmvc+jQuery EasyUI+combobox多值操作實(shí)現(xiàn)角色授權(quán),實(shí)現(xiàn)對(duì)角色role進(jìn)行授權(quán)操作,有需要的可以了解一下。2016-11-11DOM事件階段以及事件捕獲與事件冒泡先后執(zhí)行順序(圖文詳解)
DOM事件標(biāo)準(zhǔn)定義了兩種事件流,這兩種事件流有著顯著的不同并且可能對(duì)你的應(yīng)用有著相當(dāng)大的影響。這兩種事件流分別是捕獲和冒泡。和許多Web技術(shù)一樣,在它們成為標(biāo)準(zhǔn)之前,Netscape和微軟各自不同地實(shí)現(xiàn)了它們,下面介紹DOM事件階段以及事件捕獲與事件冒泡先后執(zhí)行順序2015-08-08jQuery實(shí)現(xiàn)鼠標(biāo)滑過(guò)商品小圖片上顯示對(duì)應(yīng)大圖片功能【測(cè)試可用】
這篇文章主要介紹了jQuery實(shí)現(xiàn)鼠標(biāo)滑過(guò)商品小圖片上顯示對(duì)應(yīng)大圖片功能,涉及jQuery事件響應(yīng)、元素遍歷及屬性動(dòng)態(tài)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-04-04ASP.NET jQuery 實(shí)例6 (實(shí)現(xiàn)CheckBoxList成員全選或全取消)
ASP.NET jQuery 實(shí)例6 (實(shí)現(xiàn)CheckBoxList成員全選或全取消) ,需要的朋友可以參考下。2012-01-01使用jQuery動(dòng)態(tài)設(shè)置單選框的選中效果
這篇文章主要介紹了使用jQuery動(dòng)態(tài)設(shè)置單選框的選中效果,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-12-12基于jQuery實(shí)現(xiàn)Div窗口震動(dòng)特效代碼-代碼簡(jiǎn)單
本文給大家介紹基于jiquery實(shí)現(xiàn)div窗口震動(dòng)特效代碼,需要的朋友可以參考下2015-08-08jquery計(jì)算出left和top,讓一個(gè)div水平垂直居中的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇jquery計(jì)算出left和top,讓一個(gè)div水平垂直居中的簡(jiǎn)單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-07-07