萬盛學電腦網

 萬盛學電腦網 >> 腳本專題 >> javascript >> jQuery源碼分析之Callbacks詳解

jQuery源碼分析之Callbacks詳解

 這篇文章主要分為以下知識:什麼是Callbacks、Callbacks模型、基本模塊實現、once和auto(memory)、源碼和源碼下載,十分的細致全面,這裡推薦給大家,有需要的小伙伴參考下吧。

   

代碼的本質突出順序、有序這一概念,尤其在javascript——畢竟javascript是單線程引擎。

javascript擁有函數式編程的特性,而又因為javascript單線程引擎,我們的函數總是需要有序的執行。優秀代碼常常 把函數切割成各自的模塊,然後在某一特定條件下執行,既然這些函數是有序的執行,那麼我們為什麼不編寫一個統一管理的對象,來幫助我們管理這些函數——於是,Callbacks(回調函數)誕生。

什麼是Callbacks

javascript中充斥著函數編程,例如最簡單的window.onload承接的就是一個函數,悲催的是window.onload直接賦值的話只能接收一個函數,如果有好幾個函數想要在onload中執行,那麼我們就需要編寫如下代碼:

 

代碼如下:
function a(elem) {
elem.innerHTML = '我是函數a,我要改變Element的HTML結構';
};
function b(elem) {
elem.innerHTML = '我的函數b,我要改變Element的style';
}
window.onload = function () {
var elem = document.getElementById('test');
a(elem);
b(elem);
};

 

回調函數初衷就是建立在這麼個玩意兒的上面,不再讓我們分散這些函數,而是把這些函數統一整理。可以看見,我們在window.onload中希望針對一個Element做兩件事情:先改變html結構,然後改變這個html的style。兩個函數同樣是針對一個Element操作,而這兩個函數最終的執行都是有序進行的。那麼我們為什麼不編寫一個這樣的對象管理這些函數呢。當然, 這只是回調函數的最基礎的存在意義,我們需要的不僅僅是這樣一個簡單的回調函數對象,我們需要一個更加強大的回調函數。好吧,這只是一個簡單的用例,那麼我可以告訴你這個回調函數除了一個個執行函數之外,它還可以做什麼。

Callbacks本質就是控制函數有序的執行,Javascript是單線程引擎,也就說,javascript同一時間只會有一處代碼在運行——即便是Ajax、setTimeout。 這兩個函數看起來好像都是異步的,其實並非如此,浏覽器在運行javascript代碼的時候,這些代碼都會被有序的壓入一個隊列中,當你運行Ajax的時候,浏覽器會把Ajax 壓入代碼隊列,浏覽器在處理javascript代碼是從這個代碼隊列中一個一個取代碼執行的——Callbacks,迎合了這種單線程引擎。

當然,我們要的,不僅僅是這樣一個簡單的工具對象——在jQuery源碼中,Callbacks提供了一組函數的基本管理,為Deferred(異步隊列)提供了基礎,同時也服務於Queue(同步隊列)。 Deferred用於抹平/扁平化金字塔編程(大量的回調函數嵌套,例如Ajax中需要根據請求返回碼決定執行的代碼); 而Queue,驅動著jQuery.animate(動畫引擎)。

那麼我們就來編寫一個Callbacks吧。

Callbacks模型

Array(數組):
既然我們Callbacks要承接一系列函數,那麼必然需要有一個容器。我們可以使用一個數組,並把每一個函數壓到該數組中,需要執行的時候,循環數組項執行。

工作模型:

這個Callbacks需要非常的強大,並不僅僅是壓入函數,然後執行這麼簡單,這個Callbacks應該擁有良好的執行模型。
once:當前Callbacks對象中所有的函數只會執行一次,執行一次完之後就會被釋放掉,我們可以為使用Callbacks對象的用戶提供一個穩定有效的方案,確保函數只會執行一次,之後不再執行,穩定了這些函數的線程。
auto:自動執行模型,這是個有意思的模型,有些函數依賴上一層函數,例如函數b的執行依賴於函數a,那麼我們提供一個自動執行的模型:第一次執行這個Callbacks之後,每次添加函數到Callbacks的時候,自動執行過去添加的那些函數,並把最後一次給定的參數數據傳遞給過去的那些函數,這樣就從Callbacks中抹平了這些依賴函數之間需要反復觸發的關系,這是個有意思的模型。
once&auto:我們可以讓它更強大,同時工作once和auto模型,即:當每次添加函數到Callbacks中的時候,過去的函數都會執行,然後,釋放掉這些過去的函數,下次繼續添加函數的時候,過去的那些函數不會再執行,因為once模型,已經把它們釋放掉了。

API:

add(function) - 添加一個(或多個)函數到Callbacks對象中:當然,如果你並不添加函數只是好奇看看Callbacks,我們也將讓你繼續享受你的樂趣——我們並不會拋出異常,因為這對於我們來說並不擅長。
remove(function) - 移除一個Callbacks中的一個函數:既然有了添加,那麼我們也應該提供反悔的方案,我們是多麼的平易近人,容忍著別人過去所做的一切。
has(function) - 判斷Callbacks中是否包含一個函數:哦?你竟然不確定是否包含這個函數,當初可是你丟進來的啊!你怎麼如此馬虎?不過既然你問我的話,我仍然會告訴你Callbacks是否包含這個函數,我知道你很繁忙,並不能記住和確定所有的事情。
empty() - 清空Callbacks:這些函數對於你失去了意義了麼?什麼?已經執行過你就不想要了?所以你希望可以清空它?好吧,為了內存君我還是忍下你這個需求。
disable() - 廢掉一個Callbacks:為了和別人的代碼穩定的存在,我選擇了自我犧牲——沒錯,這個方法可以廢掉Callbacks,徹底的廢掉,就如同它曾經尚未存在過一般。
disabled() - 判斷這個Callbacks是否已經被廢掉:如果你仍然不相信Callbacks是否真的自我犧牲,那麼這個方法可以讓你安心。
lock(boolean) - 鎖定這個Callbacks對象:你害怕它並不穩定,但是你又不想捨棄它,lock是個不錯的方法,它接收一個Boolean的參數,表示是否需要鎖定這個對象,當然,無參的它用於讓你確定Callbacks是否被鎖定。
fire(data) - 執行這個Callbacks中的函數:我們做的這一切,不都是為了這一刻執行的宿命麼?參數將會成為這些需要執行的函數的參數。
fireWith(context,data) - 執行Callbacks中的函數,並且指定上下文。在fire()裡,所有的函數的Context(上下文)都是Callbacks對象,而fireWidth(),可以讓你重新定義這些要執行的函數的上下文,多麼自由的編程啊,Callbacks為你考慮了一切。
fired() - 判斷這個Callbacks過去是否已經執行過:我們相信,多數時候你並不知道過去做過什麼,但是我們記錄了你做的一切,如果你過去曾經執行過這個Callbacks對象,那麼你休想否認,因為我們知道過去你是否執行了這個Callbacks。

基本模塊實現

簡單的實現:
我們先來簡單的實現一個Callbacks:

 

代碼如下:
(function (window, undefined) {
var Callbacks = function () {
//通過閉包保護這些私有變量
var list = [],//回調函數列表
fired;//是否執行過
//返回一個閉包的Callbakcs對象
return {
add: function (fn) {
//當Callbacks廢棄掉的時候,list為undefined
if (list) {
//添加一個回調函數
list.push(fn);
//支持鏈式回調
}
return this;
},
fireWith: function (context, data) {
//觸發回調函數,並指定上下文
if (list) {
fired = true;
for (var i = 0, len = list.length; i < len; i++) {
//當Callbacks中某一個函數返回false的時候,停止Callbacks後續的執行
if (list[i].apply(context, data) === false)
break;
}
}
return this;
},
fire: function () {
//觸發回調函數
//調用fireWith並指定上下文
return this.fireWith(this, arguments);
},
empty: function () {
//清空list即可
if (list)//當這個Callbacks廢棄掉的時候,Callbacks不應該可以繼續使用
list = [];
return this;
},
disable: function () {
//廢棄這個Callbacks對象,後續的回調函數列表不再執行
list = undefined;
return this;
},
disabled: function () {//檢測這個Callbacks是否已經廢掉
//轉換為boolean返回
return !list;
},
fired: function () {//這個callbacks是否執行過
return !!fired;
}
};
};
//注冊到window下
window.Callbacks = Callbacks;
}(window));

 

然後我們測試一下這個Callbacks:

 

代碼如下:
var test = new Callbacks();
test.add(function (value) {
console.log('函數1,value是:' + value);
});
test.add(function (value) {
console.log('函數2,value是:' + value);
});
test.fire('這是函數1和函數2的值');
console.log('查看函數是否執行過:' + test.fired());
test.disable();//廢棄這個Callbacks
console.log('查看函數是否被廢棄:' + test.disabled());
test.add(function () {
console.log('添加第三個函數,這個函數不應該被執行');
});
test.fire();

 

打開浏覽器的控制台我們可以看見運行結果正常。

once和auto(memory)實現

once:
once讓這個callbacks中的函數運行一次

copyright © 萬盛學電腦網 all rights reserved