jQuery-v2.0.3源碼淺析03-Callbacks

廢話不多說我們今天來看下jQuery的Callbacks函數(shù)霎褐。
在看Callbacks源碼之前,我們先來看看Callbacks的簡單使用吧功炮。
Callbacks用來統(tǒng)一管理函數(shù)的全度。

function aaa(){
    console.log(1);
}
function bbb(){
    console.log(2);
}
var cb= $.Callbacks();
cb.add(aaa);
cb.add(bbb);
cb.fire();

以上代碼執(zhí)行完畢之后,在控制臺會依次輸出1和2鹦赎。
如果用傳統(tǒng)的方式

function aaa(){
    console.log(1);
}
function bbb(){
    console.log(2);
}
aaa();
bbb();

對比之后可以看出最終函數(shù)的執(zhí)行callbacks只需要調(diào)用fire就可以依次執(zhí)行add添加的函數(shù)了。
當然這只是callbacks最簡單的使用误堡。

我們再來看一個例子

function aaa(){
  console.log(1);
}
(function(){
  function bbb(){
    console.log(2);
  }
})();
aaa();
bbb();

這個時候會發(fā)現(xiàn)bbb根本找不到古话,因為bbb函數(shù)定義在子作用域里面
我們再來看看callbacks的寫法

var cb= $.Callbacks();
function aaa(){
    console.log(1);
}
cb.add(aaa);
(function(){
    function bbb(){
        console.log(2);
    }
    cb.add(bbb);
})();
cb.fire();

我們發(fā)現(xiàn)只要我們把cb定義在全局就可以解決這種作用域問題,是不是很方便锁施。

當然再看源碼之前我們可以自己模擬一下這個callbacks函數(shù)

function callbacks(){
    var list = [],
        self = {
            add: function(fn){
                list.push(fn);
            },
            fire: function(){
                for(var i = 0; i < list.length; ++i){
                    var item = list[i];
                    item && item();
                }
            }
        }
    return self;
}
function a(){
    console.log(1);
}
function b(){
    console.log(2);
}
var cb = callbacks();
cb.add(a);
cb.add(b);
cb.fire();

我們可以看到控制臺依次輸出了1和2陪踩。
其實Callbacks最基礎(chǔ)的實現(xiàn)部分就是這樣的。

讓我們再看看Callbacks的參數(shù)吧悉抵,Callbacks可以接受'once'肩狂、'memory'、'unique'姥饰、'stopOnFalse'這種類型傻谁,還可以4種類型隨便組合用空格隔開例如'once memory'。

先看once參數(shù)吧列粪,從單詞意思可以看出來這個參數(shù)的意思就是讓add的方法只執(zhí)行一次审磁,例如:

function a(){
    console.log(1);
}
function b(){
    console.log(2);
}
var cb = $.Callbacks();
cb.add(a);
cb.add(b);
cb.fire();
cb.fire();
//如果不傳參數(shù)控制臺會輸出1谈飒、2、1态蒂、2 杭措,如果這樣調(diào)用
var cb = $.Callbacks(‘once’);
//會發(fā)現(xiàn)控制臺只會輸出一遍1、2

這個時候我們可以嘗試修改自己的callbacks函數(shù)來實現(xiàn)這個效果

function createOptions(options){
    var object = {};
    var list = options.split(' ') || [];
    for(let i = 0; i < list.length; ++i){
        object[list[i]] = true;
    }
    return object;
}
function callbacks( options ){
    //轉(zhuǎn)換一下參數(shù)方便判斷
    options = createOptions(options);

    var firingIndex,
        firingLength,
        list = [],
        self = {
            add: function(fn){
                list.push(fn);
            },
            fire: function(){
                firingLength = list.length;
                if(!options.once || options.once && firingIndex == null) firingIndex = 0;

                for(;list && firingIndex < firingLength; ++firingIndex){
                    var item = list[firingIndex];
                    item && item();
                }
            }
        }
    return self;
}
function a(){
    console.log(1);
}
function b(){
    console.log(2);
}
var cb = callbacks('once');
cb.add(a);
cb.add(b);
cb.fire();
cb.fire();

然后我們再來看一下比較簡單的一個參數(shù)'stopOnFalse'钾恢,stopOnFalse的作用就是當list里面函數(shù)執(zhí)行完畢之后如果返回false就停止執(zhí)行瓤介,例如:

function a(){
    console.log(1);
    return false;
}
function b(){
    console.log(2);
}
var cb = $.Callbacks('stopOnFalse');
cb.add(a);
cb.add(b);
cb.fire();

控制臺只會輸出1

我們也可以改造一下我們的代碼來實現(xiàn)這個效果

function createOptions(options){
    var object = {};
    var list = options.split(' ') || [];
    for(let i = 0; i < list.length; ++i){
        object[list[i]] = true;
    }
    return object;
}
function callbacks( options ){
    //轉(zhuǎn)換一下參數(shù)方便判斷
    options = createOptions(options);

    var firingIndex,
        firingLength,
        list = [],
        self = {
            add: function(fn){
                list.push(fn);
            },
            fire: function(){
                firingLength = list.length;
                if(!options.once || options.once && firingIndex == null) firingIndex = 0;

                for(;list && firingIndex < firingLength; ++firingIndex){
                    var item = list[firingIndex];
                    if(item && item() === false && options.stopOnFalse){
                        break;
                    }
                }
            }
        }
    return self;
}
function a(){
    console.log(1);
    return false;
}
function b(){
    console.log(2);
}
var cb = callbacks('stopOnFalse');
cb.add(a);
cb.add(b);
cb.fire();

接下來我們看看'memory'的用法,memory的作用是當fire方法調(diào)用之后赘那,我們再使用add添加新的函數(shù)的時候直接執(zhí)行刑桑。

function a(){
    console.log(1);
}
function b(){
    console.log(2);
}
var cb = $.Callbacks();
cb.add(a);
cb.fire();
cb.add(b);
//發(fā)現(xiàn)控制臺只會打印1
//接下來我們來改一下參數(shù)
var cb = $.Callbacks('memory');
//這個時候控制臺會輸出1和2

這個時候按照我們自己的思路來設(shè)計的話處理邏輯是不是應(yīng)該放在add方法中,判斷一下當前狀態(tài)如果已經(jīng)fire過的話募舟,就直接執(zhí)行,
我們接著來改造一下我們的代碼

function createOptions(options){
    var object = {};
    var list = options.split(' ') || [];
    for(let i = 0; i < list.length; ++i){
        object[list[i]] = true;
    }
    return object;
}
function callbacks( options ){
    //轉(zhuǎn)換一下參數(shù)方便判斷
    options = createOptions(options);

    var firingIndex,
        firingLength,
        list = [],
        fired,
        self = {
            add: function(fn){
                if(fired){
                    fn && fn();
                }else{
                    list.push(fn);
                }
            },
            fire: function(){
                fired = true;
                firingLength = list.length;
                if(!options.once || options.once && firingIndex == null) firingIndex = 0;

                for(;list && firingIndex < firingLength; ++firingIndex){
                    var item = list[firingIndex];
                    if(item && item() === false && options.stopOnFalse){
                        break;
                    }
                }
            }
        }
    return self;
}
function a(){
    console.log(1);
}
function b(){
    console.log(2);
}
var cb = callbacks('memory');
cb.add(a);
cb.fire();
cb.add(b);

接下來我們再看一下'unique'參數(shù)祠斧,unique參數(shù)的作用就是控制add進來的函數(shù)不能有重復(fù)的。例如:

function a(){
    console.log(1);
}
var cb = $.Callbacks();
cb.add(a);
cb.add(a);
cb.fire();
//執(zhí)行完畢之后控制臺會輸出兩個1
//我們來改一下參數(shù)
var cb = $.Callbacks('unique');
//控制臺只會輸出一個1

我們來該找一下自己的callbacks吧

function createOptions(options){
    var object = {};
    var list = options.split(' ') || [];
    for(let i = 0; i < list.length; ++i){
        object[list[i]] = true;
    }
    return object;
}
function callbacks( options ){
    //轉(zhuǎn)換一下參數(shù)方便判斷
    options = createOptions(options);

    var firingIndex,
        firingLength,
        list = [],
        fired,
        self = {
            add: function(fn){
                if(fired){
                    fn && fn();
                }else{
                    if(options.unique && list.indexOf(fn) > -1){
                        return;
                    }
                    list.push(fn);
                }
            },
            fire: function(){
                fired = true;
                firingLength = list.length;
                if(!options.once || options.once && firingIndex == null) firingIndex = 0;

                for(;list && firingIndex < firingLength; ++firingIndex){
                    var item = list[firingIndex];
                    if(item && item() === false && options.stopOnFalse){
                        break;
                    }
                }
            }
        }
    return self;
}
function a(){
    console.log(1);
}
var cb = callbacks('unique');
cb.add(a);
cb.add(a);
cb.fire();

上面我們對callbacks進行了簡單實現(xiàn)拱礁,但還有非常多的情況沒有判斷琢锋,我們來看一下jQuery是如何處理的吧。
源碼注釋版

//緩存options下次如果又傳了同樣的參數(shù)就不需要重新計算一邊了呢灶,提高性能類似 {'once memory': { 'once': true, 'memory': true }}
var optionsCache = {};
// 將字符串類型的options轉(zhuǎn)換成對象吴超,并且緩存起來
function createOptions( options ) {
    var object = optionsCache[ options ] = {};
    jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
        object[ flag ] = true;
    });
    return object;
}
jQuery.Callbacks = function( options ) {

    //轉(zhuǎn)換options對象,options還可以接收類似 { 'once': true, 'memory': true } 的對象
    options = typeof options === "string" ?
        ( optionsCache[ options ] || createOptions( options ) ) :
        jQuery.extend( {}, options );

    var memory,
        fired,
        firing,
        firingStart,
        firingLength,
        firingIndex,
        list = [],
        stack = !options.once && [],//如果設(shè)置了once則stack=false鸯乃,如果沒有設(shè)置once則stack=[]可以存儲后續(xù)操作
        fire = function( data ) {
            memory = options.memory && data;//如果設(shè)置了memory則memory = data鲸阻,否則memory=false
            fired = true;//如果執(zhí)行過fire函數(shù)就把fired設(shè)置成true,代表已經(jīng)fire過了
            firingIndex = firingStart || 0;
            firingStart = 0;
            firingLength = list.length;
            firing = true;//fire過程中記錄一下狀態(tài)
            for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {//如果函數(shù)執(zhí)行完畢之后返回false并且設(shè)置了stopOnFalse缨睡,直接跳出循環(huán)
                    memory = false; 
                    break;
                }
            }
            firing = false;//fire完畢把狀態(tài)改回來
            if ( list ) {//如果這個Callbacks沒有銷毀
                if ( stack ) {//在存在在fire過程中調(diào)用fire鸟悴,在fire完畢之后再執(zhí)行
                    if ( stack.length ) {
                        fire( stack.shift() );
                    }
                } else if ( memory ) {//設(shè)置了once又設(shè)置了memory才會執(zhí)行將list清空確保不重復(fù)執(zhí)行
                    list = [];
                } else {//如果設(shè)置once,沒有設(shè)置memory 則在一次執(zhí)行完畢之后 銷毀Callbacks
                    self.disable();
                }
            }
        },
        self = {
            add: function() {
                if ( list ) {
                    var start = list.length;//記錄list下標奖年,在使用'memory'參數(shù)的時候使用
                    (function add( args ) {//循環(huán)push函數(shù)
                        jQuery.each( args, function( _, arg ) {
                            var type = jQuery.type( arg );
                            if ( type === "function" ) {
                                if ( !options.unique || !self.has( arg ) ) {//如果使用了'unique'并且添加重復(fù)函數(shù)會直接忽略
                                    list.push( arg );
                                }
                            } else if ( arg && arg.length && type !== "string" ) {//add也可以接收一個數(shù)組或者類似數(shù)組
                                add( arg );
                            }
                        });
                    })( arguments );
                    if ( firing ) {
                        /**
                            考慮到在fire過程中细诸,發(fā)現(xiàn)函數(shù)里面調(diào)用了add方法,這個時候需要更新一下循環(huán)長度
                            例如:
                            function a(){
                                console.log(1);
                                funciton b(){
                                    console.log(2);
                                }
                                cb.add(b);
                            }
                        **/
                        firingLength = list.length;
                    } else if ( memory ) {//如果fire過一次并且設(shè)置了memory
                        firingStart = start;
                        fire( memory );
                    }
                }
                return this;
            },
            remove: function() {//移除list中的某一函數(shù)
                if ( list ) {
                    jQuery.each( arguments, function( _, arg ) {
                        var index;
                        while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                            list.splice( index, 1 );
                            if ( firing ) {//判斷一下是否在fire過程中進行了remove
                                if ( index <= firingLength ) {
                                    firingLength--;
                                }
                                if ( index <= firingIndex ) {
                                    firingIndex--;
                                }
                            }
                        }
                    });
                }
                return this;
            },
            has: function( fn ) {//判斷數(shù)組中是否包含fn
                return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
            },
            empty: function() {//清空list陋守,可以重新添加調(diào)用
                list = [];
                firingLength = 0;
                return this;
            },
            disable: function() {//銷毀掉這個Callbacks震贵,不可進行其他操作
                list = stack = memory = undefined;
                return this;
            },
            disabled: function() {//判斷是否被銷毀
                return !list;
            },
            lock: function() {//鎖住
                stack = undefined;
                if ( !memory ) {
                    self.disable();
                }
                return this;
            },
            locked: function() {//判斷是否鎖住了
                return !stack;
            },
            fireWith: function( context, args ) {//處理一下參數(shù),可以設(shè)置回調(diào)函數(shù)的最終context
                if ( list && ( !fired || stack ) ) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];
                    if ( firing ) {//如果fire過程中又調(diào)用了fire先存儲起來
                        stack.push( args );
                    } else {//調(diào)用fire函數(shù)水评,并傳入?yún)?shù)
                        fire( args );
                    }
                }
                return this;
            },
            fire: function() {
                self.fireWith( this, arguments );
                return this;
            },
            fired: function() {//判斷是否fire過
                return !!fired;
            }
        };

    return self;
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猩系,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子之碗,更是在濱河造成了極大的恐慌蝙眶,老刑警劉巖季希,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件褪那,死亡現(xiàn)場離奇詭異幽纷,居然都是意外死亡,警方通過查閱死者的電腦和手機博敬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門友浸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人偏窝,你說我怎么就攤上這事收恢。” “怎么了祭往?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵伦意,是天一觀的道長。 經(jīng)常有香客問我硼补,道長驮肉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任已骇,我火速辦了婚禮离钝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘褪储。我一直安慰自己卵渴,他們只是感情好,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布鲤竹。 她就那樣靜靜地躺著浪读,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辛藻。 梳的紋絲不亂的頭發(fā)上瑟啃,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音揩尸,去河邊找鬼蛹屿。 笑死,一個胖子當著我的面吹牛岩榆,可吹牛的內(nèi)容都是我干的错负。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼勇边,長吁一口氣:“原來是場噩夢啊……” “哼犹撒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起粒褒,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤识颊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祥款,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡清笨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了刃跛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抠艾。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖桨昙,靈堂內(nèi)的尸體忽然破棺而出检号,到底是詐尸還是另有隱情,我是刑警寧澤蛙酪,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布齐苛,位于F島的核電站,受9級特大地震影響桂塞,放射性物質(zhì)發(fā)生泄漏脸狸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一藐俺、第九天 我趴在偏房一處隱蔽的房頂上張望炊甲。 院中可真熱鬧,春花似錦欲芹、人聲如沸卿啡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颈娜。三九已至,卻和暖如春浙宜,著一層夾襖步出監(jiān)牢的瞬間官辽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工粟瞬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留同仆,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓裙品,卻偏偏與公主長得像俗批,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子市怎,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容