$.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
- 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ū)分大小寫
- 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”。
- 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í)行
- 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
的功能摄咆。