廢話不多說我們今天來看下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;
};