D005+技術(shù)|jQuery-$.Callbacks()實現(xiàn)原理

$.Callbacks用于管理函數(shù)隊列顾彰,通過add添加處理函數(shù)到隊列中沪蓬,通過fire去執(zhí)行這些處理函數(shù)。

本節(jié)向大家介紹$.Callbacks的實現(xiàn)的原理,并簡單實現(xiàn)一個自己的callbacks胳搞。

概念解讀

從事件函數(shù)了解Callbacks绷落,事件通常與函數(shù)配合使用姥闪,這樣就可以通過觸發(fā)事件來驅(qū)動函數(shù)的執(zhí)行。
原則上砌烁,一個事件對應(yīng)一個事件函數(shù)甘畅。在一個事件對應(yīng)多個事件函數(shù)的情況下,后者會覆蓋前者往弓。

ele.onclick = function(){
    console.log("code")
}
ele.onclick = function(){
    console.log("code1")
}

上邊這個Demo中后面綁定的這個事件函數(shù)會覆蓋前邊的,事件觸發(fā)時會打印"code1"蓄氧。

事件驅(qū)動改造

如果想讓觸發(fā)事件時執(zhí)行多個函數(shù)函似,是否可行呢?

當(dāng)然可以喉童,我們可以把需要執(zhí)行的多個函數(shù)放在一個數(shù)組里撇寞,事件觸發(fā)時循環(huán)執(zhí)行這個數(shù)組里的函數(shù)。
下面看一下偽代碼:

var callbacks = [function a(){}, function b(){}, function c(){}];
ele.onclick = function(){
    var _this = this;
    callbacks.forEach(function(fn){
        fn.call(_this);
    });
}

而Callbacks并不僅僅是一個數(shù)組堂氯,而是一個容器蔑担。

$.Callbacks API的使用

基礎(chǔ)應(yīng)用


// 1. $.Callbacks()返回Callbacks的實例對象
var cb = $.Callbacks();
// 2. 方法add()向內(nèi)部隊列添加函數(shù)
cb.add(() => {
    console.log(1)
});
// 3. 方法fire()傳入?yún)?shù)執(zhí)行隊列里的函數(shù)
cb.fire();
// 控制臺結(jié)果:1

Callbacks參數(shù)

$.Callbacks()通過字符串參數(shù)的形式,支持4種特定的功能:once,unique,stopOnFalse,memory

  1. once 函數(shù)隊列只執(zhí)行一次
// 不添加參數(shù)
var cb = $.Callbacks();
cb.add(() => {
    console.log(1)
});
cb.fire(); // 控制臺打友拾住: 1
cb.fire(); // 控制臺打悠∥铡: 1

如果不指定參數(shù),調(diào)用fire()方法兩次晶框,控制臺將打印兩次1

// 添加參數(shù)
var cb = $.Callbacks("once");
cb.add(() => {
    console.log(1)
});
cb.fire(); // 控制臺打优盘А: 1
cb.fire();

如果不指定參數(shù)為字符串once,調(diào)用fire()方法兩次授段,控制臺只打印一次1蹲蒲,需要注意:這里的字符串是區(qū)分大小寫

  1. unique 使添加到內(nèi)部函數(shù)隊列里的函數(shù)保持唯一,不能重復(fù)添加
// 不加參數(shù) 
var cb = $.Callbacks();
var test = function () {
    console.log("unique test");
};
cb.add(test, test);
cb.fire(); 
// 控制臺打忧止蟆: 
// unique test  
// unique test 

我們通過add()方法往函數(shù)隊列里添加了兩個test函數(shù)届搁,調(diào)用fire()方法,控制臺打印了兩次“unique test”窍育。

// 不加參數(shù) 
var cb = $.Callbacks(“unique”);
var test = function () {
    console.log("unique test");
};
cb.add(test, test);
cb.fire(); // 控制臺打涌馈: unique test 

指定參數(shù)unique,即使我們往函數(shù)隊列里添加了兩個test函數(shù)漱抓,調(diào)用fire()方法么翰,控制臺也只會打印一次“unique test”。

  1. stopOnFalse 內(nèi)部函數(shù)隊列順序依次執(zhí)行辽旋,當(dāng)某個函數(shù)返回值為false時浩嫌,停止該函數(shù)后邊的函數(shù)繼續(xù)執(zhí)行
// 不添加參數(shù)
var cb = $.Callbacks();
var test1 = function () {
    console.log("stopOnFalse one");
};
var test2 = function () {
    console.log("stopOnFalse two");
    return false;
};
var test3 = function () {
    console.log("stopOnFalse three");
};
cb.add(test1, test2, test3);
cb.fire()
// 控制臺打娱艹佟:
// stopOnFalse one
// stopOnFalse two
// stopOnFalse three

不添加參數(shù)時函數(shù)隊列依次執(zhí)行

// 添加參數(shù)
var cb = $.Callbacks("stopOnFalse");
var test1 = function () {
    console.log("stopOnFalse one");
};
var test2 = function () {
    console.log("stopOnFalse two");
    return false;
};
var test3 = function () {
    console.log("stopOnFalse three");
};
cb.add(test1, test2, test3);
cb.fire()
// 控制臺打印:
// stopOnFalse one
// stopOnFalse two

指定參數(shù)為stopOnFalse码耐,當(dāng)函數(shù)執(zhí)行到test2時追迟,因為該函數(shù)返回了false,所以后邊的test3將不再執(zhí)行

  1. memory 當(dāng)函數(shù)隊列fire一次過后骚腥,內(nèi)部會記錄當(dāng)前fire方法的參數(shù)敦间。當(dāng)下次調(diào)用add時,會把記錄的參數(shù)傳遞給新添加的函數(shù)并立即執(zhí)行這個新添加的函數(shù)束铭。
// 不添加參數(shù)
var cb = $.Callbacks();
var test1 = function () {
    console.log("memory one");
};
var test2 = function () {
    console.log("memory two");
};
cb.add(test1);
cb.fire(); // 控制臺打永椤:memory one
cb.add(test2);
// 添加參數(shù)
var cb = $.Callbacks("memory");
var test1 = function () {
    console.log("memory one");
};
var test2 = function () {
    console.log("memory two");
};
cb.add(test1);
cb.fire(); // 控制臺打印:memory one memory two
cb.add(test2);

$.Callbacks 實現(xiàn)

參考jQuery的Callbacks契沫,我們自己來實現(xiàn)一下带猴。

基本結(jié)構(gòu)

(function (root) {
    var _ = {
        callbacks: function () {
            console.log("test");
        }
    }
    root._ = _;
})(this);
_.callbacks(); // test

通過一個閉包把執(zhí)行上下文傳入,因為在瀏覽器里執(zhí)行這里傳入的是Window對象懈万,把_做為root的屬性拴清,這樣我們在全局就可以訪問_

(function (root) {
    var optionsCache = {};
    var _ = {
        callbacks: function (options) {
            // 判斷傳入的參數(shù)類型
            // 字符串:存儲在optionsCache對象里会通,如果該對象已經(jīng)存在這個屬性直接使用口予,如果不存在則創(chuàng)建這個屬性值為true
            options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};

            console.log(options); // {"once":true,"memory":true}
            console.log(optionsCache) // {"once":true,"memory":true}
        }

    }
    root._ = _;
    var createOptions = function (options) {
        var object = {};
        // 如果同時指定兩種類型,傳入的參數(shù)為“once memory”涕侈,需拆解成單獨的字符串
        options.split(/\s+/).forEach(value => {
            object[value] = optionsCache[value] = true;
        });
        return object;
    }
})(this);
_.callbacks("once memory");

上邊這個函數(shù)支持獲取用戶傳過來的參數(shù)沪停,并且支持用戶同時指定多個類型的參數(shù),并把這些參數(shù)存儲在optionsCache緩存對象中裳涛。接下來看一下add(),fire()方法的實現(xiàn)牙甫。

(function (root) {
    var optionsCache = {};
    var _ = {
        callbacks: function (options) {
            // 判斷傳入的參數(shù)類型
            // 字符串:存儲在optionsCache對象里,如果該對象已經(jīng)存在這個屬性直接使用调违,如果不存在則創(chuàng)建這個屬性值為true
            options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};

            var self = {
                add: function () {
                    console.log("add");
                },
                fire: function () {
                    console.log("fire");
                }
            }
            return self;
        }

    }
    root._ = _;
    var createOptions = function (options) {
        var object = {};
        // 如果同時指定兩種類型窟哺,傳入的參數(shù)為“once memory”,需拆解成單獨的字符串
        options.split(/\s+/).forEach(value => {
            object[value] = optionsCache[value] = true;
        });
        return object;
    }
})(this);
var cb = _.callbacks();
cb.add(); // add
cb.fire(); // fire   

在callbacks函數(shù)中創(chuàng)建self對象技肩,并添加add方法和fire方法且轨,然后把這個self返回,這樣調(diào)用callbacks函數(shù)后就可以獲取這個self虚婿。參考上面的代碼旋奢。下一步,我們來實現(xiàn)一下add方法和fire方法然痊。

add方法和fire方法的實現(xiàn)

(function (root) {
    var optionsCache = {};
    var _ = {
        callbacks: function (options) {
            // 判斷傳入的參數(shù)類型是否為字符串
            // 存儲在optionsCache對象里至朗,如果該對象已經(jīng)存在這個屬性直接使用,如果不存在則創(chuàng)建這個屬性值為true
            options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};

            var fnList = [];
            var index, length;
            var fire = function (data) {
                index = 0;
                length = fnList.length;
                for (; index < length; index++) {
                    fnList[index].apply(data[0], data[1]);
                }
            }
            var self = {
                add: function () {
                    // 將傳入的參數(shù)轉(zhuǎn)成數(shù)組
                    var argArr = Array.prototype.slice.call(arguments);
                    argArr.forEach((fn) => {
                        // 判斷傳入的參數(shù)是否為function
                        if (toString.call(fn) === "[object Function]") {
                            fnList.push(fn);
                        }
                    })
                },
                fireWith: function (context, arguments) {
                    var args = [context, arguments];
                    fire(args);
                },
                fire: function () {
                    self.fireWith(this, arguments);
                }
            }
            return self;
        }
    }
    root._ = _;
    var createOptions = function (options) {
        var object = {};
        // 如果同時指定兩種類型剧浸,傳入的參數(shù)為“once memory”锹引,需拆解成單獨的字符串
        options.split(/\s+/).forEach(value => {
            object[value] = optionsCache[value] = true;
        });
        return object;
    }
})(this);

var cb = _.callbacks();
cb.add(function a(params) {
    console.log("a");
}, function b(params) {
    console.log("b");
}); 
cb.fire(); // a b

add()可以往函數(shù)隊列里添加函數(shù)矗钟,fire()可以依次執(zhí)行隊列的函數(shù)。上面的代碼fire()是順序執(zhí)行隊列嫌变,接下來實現(xiàn)通過指定callbacks的參數(shù)來控制函數(shù)隊列的執(zhí)行吨艇。

參數(shù)stopOnFalse功能

(function (root) {
    var optionsCache = {};
    var _ = {
        callbacks: function (options) {
            // 判斷傳入的參數(shù)類型是否為字符串
            // 存儲在optionsCache對象里,如果該對象已經(jīng)存在這個屬性直接使用腾啥,如果不存在則創(chuàng)建這個屬性值為true
            options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};

            var fnList = [];
            var index, length;
            var fire = function (data) {
                index = 0;
                length = fnList.length;
                for (; index < length; index++) {
                    // 判斷函數(shù)執(zhí)行結(jié)果是否為false并且設(shè)置了stopOnFalse
                    // data[0]:執(zhí)行上下文
                    // data[1]:執(zhí)行時傳入的參數(shù)
                    if (!(fnList[index].apply(data[0], data[1])) && options['stopOnFalse']) {
                        break;
                    }
                }
            }
            var self = {
                add: function () {
                    // 將傳入的參數(shù)轉(zhuǎn)成數(shù)組
                    var argArr = Array.prototype.slice.call(arguments);
                    argArr.forEach((fn) => {
                        // 判斷傳入的參數(shù)是否為function
                        if (toString.call(fn) === "[object Function]") {
                            fnList.push(fn);
                        }
                    })
                },
                fireWith: function (context, arguments) {
                    var args = [context, arguments];
                    fire(args);
                },
                fire: function () {
                    self.fireWith(this, arguments);
                }
            }
            return self;
        }
    }
    root._ = _;
    var createOptions = function (options) {
        var object = {};
        // 如果同時指定兩種類型东涡,傳入的參數(shù)為“once memory”,需拆解成單獨的字符串
        options.split(/\s+/).forEach(value => {
            object[value] = optionsCache[value] = true;
        });
        return object;
    }
})(this);
// 不指定參數(shù)
var cb = _.callbacks();
cb.add(function a(params) {
    console.log("a");
    return false;
}, function b(params) {
    console.log("b");
}); 
cb.fire(); // a b
// 指定參數(shù)為stopOnFalse
var cb = _.callbacks("stopOnFalse");
cb.add(function a(params) {
    console.log("a");
    return false;
}, function b(params) {
    console.log("b");
}); 
cb.fire(); // a

上面代碼實現(xiàn)了calLbacks指定參數(shù)為stopOnFalse時的效果倘待。

參數(shù)once功能

(function (root) {
    var optionsCache = {};
    var _ = {
        callbacks: function (options) {
            // 判斷傳入的參數(shù)類型是否為字符串
            // 存儲在optionsCache對象里疮跑,如果該對象已經(jīng)存在這個屬性直接使用,如果不存在則創(chuàng)建這個屬性值為true
            options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};

            var fnList = [];
            var index, length, isFire;
            var fire = function (data) {
                index = 0;
                length = fnList.length;
                for (; index < length; index++) {
                    // 判斷函數(shù)執(zhí)行結(jié)果是否為false并且設(shè)置了stopOnFalse
                    // data[0]:執(zhí)行上下文
                    // data[1]:執(zhí)行時傳入的參數(shù)
                    if (!(fnList[index].apply(data[0], data[1])) && options['stopOnFalse']) {
                        break;
                    }
                }
                isFire = true;
            }
            var self = {
                add: function () {
                    // 將傳入的參數(shù)轉(zhuǎn)成數(shù)組
                    var argArr = Array.prototype.slice.call(arguments);
                    argArr.forEach((fn) => {
                        // 判斷傳入的參數(shù)是否為function
                        if (toString.call(fn) === "[object Function]") {
                            fnList.push(fn);
                        }
                    })
                },
                fireWith: function (context, arguments) {
                    var args = [context, arguments];
                    if (!(options["once"] && isFire)) {
                        fire(args);
                    }

                },
                fire: function () {
                    self.fireWith(this, arguments);
                }
            }
            return self;
        }
    }
    root._ = _;
    var createOptions = function (options) {
        var object = {};
        // 如果同時指定兩種類型凸舵,傳入的參數(shù)為“once memory”祖娘,需拆解成單獨的字符串
        options.split(/\s+/).forEach(value => {
            object[value] = optionsCache[value] = true;
        });
        return object;
    }
})(this);
// 不添加參數(shù)
var cb = _.callbacks();
cb.add(function a(params) {
    console.log("a");
    return false;
}, function b(params) {
    console.log("b");
});
cb.fire(); // ab
cb.fire(); // ab
// 添加參數(shù)once
var cb = _.callbacks("once");
cb.add(function a(params) {
    console.log("a");
    return false;
}, function b(params) {
    console.log("b");
});
cb.fire(); // ab
cb.fire(); 

上面的代碼實現(xiàn)了當(dāng)設(shè)置參數(shù)為once時,fire調(diào)用多次贞间,只會執(zhí)行1次。

參數(shù)memory功能

(function (root) {
    var optionsCache = {};
    var _ = {
        callbacks: function (options) {
            // 判斷傳入的參數(shù)類型是否為字符串
            // 存儲在optionsCache對象里雹仿,如果該對象已經(jīng)存在這個屬性直接使用增热,如果不存在則創(chuàng)建這個屬性值為true
            options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};

            var fnList = [];
            var index, length, isFire, memory, start;
            var fire = function (data) {
                // 如果設(shè)置"memory",則記錄當(dāng)前傳入的參數(shù)
                memory = options["memory"] && data;
                index = start || 0;
                start = 0
                length = fnList.length;
                for (; index < length; index++) {
                    // 判斷函數(shù)執(zhí)行結(jié)果是否為false并且設(shè)置了stopOnFalse
                    // data[0]:執(zhí)行上下文
                    // data[1]:執(zhí)行時傳入的參數(shù)
                    if (fnList[index].apply(data[0], data[1]) === false && options['stopOnFalse']) {
                        break;
                    }
                }
                isFire = true;
            }
            var self = {
                add: function () {
                    // 將傳入的參數(shù)轉(zhuǎn)成數(shù)組
                    var argArr = Array.prototype.slice.call(arguments);
                    argArr.forEach((fn) => {
                        // 判斷傳入的參數(shù)是否為function
                        if (toString.call(fn) === "[object Function]") {
                            fnList.push(fn);
                        }
                    })

                    // 如果設(shè)置了memory參數(shù)胧辽,并且參數(shù)存在峻仇,則調(diào)用
                    if (memory) {
                        // start為次一次執(zhí)行時的順序
                        start = fnList.length - 1;
                        fire(memory)
                    }
                },
                fireWith: function (context, arguments) {
                    var args = [context, arguments];
                    if (!(options["once"] && isFire)) {
                        fire(args);
                    }
                },
                fire: function () {
                    self.fireWith(this, arguments);
                }
            }
            return self;
        }
    }
    root._ = _;
    var createOptions = function (options) {
        var object = {};
        // 如果同時指定兩種類型,傳入的參數(shù)為“once memory”邑商,需拆解成單獨的字符串
        options.split(/\s+/).forEach(value => {
            object[value] = optionsCache[value] = true;
        });
        return object;
    }
})(this);

var cb = _.callbacks("memory");
cb.add(function a(params) {
    console.log("a");
    return false;
});
cb.add(function c(params) {
    console.log("c");
}, function d(params) {
    console.log("d");
})
cb.fire(); // a c d b
cb.add(function b(params) {
    console.log("b");
})

上面代碼實現(xiàn)了memory的功能摄咆。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市人断,隨后出現(xiàn)的幾起案子吭从,更是在濱河造成了極大的恐慌,老刑警劉巖恶迈,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涩金,死亡現(xiàn)場離奇詭異,居然都是意外死亡暇仲,警方通過查閱死者的電腦和手機步做,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奈附,“玉大人全度,你說我怎么就攤上這事〕饴耍” “怎么了将鸵?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵勉盅,是天一觀的道長。 經(jīng)常有香客問我咨堤,道長菇篡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任一喘,我火速辦了婚禮驱还,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘凸克。我一直安慰自己议蟆,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布萎战。 她就那樣靜靜地躺著咐容,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蚂维。 梳的紋絲不亂的頭發(fā)上戳粒,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音虫啥,去河邊找鬼蔚约。 笑死,一個胖子當(dāng)著我的面吹牛涂籽,可吹牛的內(nèi)容都是我干的苹祟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼评雌,長吁一口氣:“原來是場噩夢啊……” “哼树枫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起景东,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤砂轻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后斤吐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舔清,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年曲初,在試婚紗的時候發(fā)現(xiàn)自己被綠了体谒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡臼婆,死狀恐怖抒痒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情颁褂,我是刑警寧澤故响,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布傀广,位于F島的核電站,受9級特大地震影響彩届,放射性物質(zhì)發(fā)生泄漏伪冰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一樟蠕、第九天 我趴在偏房一處隱蔽的房頂上張望贮聂。 院中可真熱鬧,春花似錦寨辩、人聲如沸吓懈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耻警。三九已至,卻和暖如春甸怕,著一層夾襖步出監(jiān)牢的瞬間甘穿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工梢杭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留温兼,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓式曲,卻偏偏與公主長得像妨托,于是被迫代替她去往敵國和親缸榛。 傳聞我的和親對象是個殘疾皇子吝羞,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

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

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,096評論 1 32
  • ??JavaScript 是一種極其靈活的語言钧排,具有多種使用風(fēng)格。 ??一般來說均澳,編寫 JavaScript 要么...
    霜天曉閱讀 744評論 0 0
  • ORA-00001: 違反唯一約束條件 (.) 錯誤說明:當(dāng)在唯一索引所對應(yīng)的列上鍵入重復(fù)值時恨溜,會觸發(fā)此異常。 O...
    我想起個好名字閱讀 5,306評論 0 9
  • 引入個人庫沖突解決: 將$引用的對象映射回原始的對象找前。相當(dāng)于引入了兩個$選擇器的jsjQuery.noConfli...
    仰望天空的人閱讀 125評論 0 1
  • 新的一天又開始了糟袁,今天沒干別的,還是陪他背課文給他抽查古詩背誦情況躺盛,這是他自己的要求项戴,我說用不用復(fù)習(xí)一下,...
    花知花落閱讀 164評論 0 0