配色: 字号:
深入jQuery中的Callbacks()
2016-12-02 | 阅:  转:  |  分享 
  
深入jQuery中的Callbacks()



引入

初看Callbacks函数很不起眼,但仔细一瞅,发现Callbacks函数是构建jQuery大厦的无比重要的一个基石。jQuery中几乎所有有关异步的操作都会用到Callbacks函数。



为什么搞了个Callbacks函数?

1在js开发中,经常会遇到同步和异步这两个概念。



2在javascript中神马是同步?神马是异步?听我讲一个相亲的故事(本故事并不准确,仅供参考):



1藤篮是一个漂亮姑娘,明年就要30岁了可现在还没有对象,于是,她的母亲给她报名了两家相亲机构,一家名叫同步相亲机构,另一家叫异步相亲机构。



2同步相亲机构:这个机构的负责人很呆板,严格遵从“先来后到”的理念。



负责人首先给藤篮一个小册子,里面记录了很多的男士资料,让藤篮从里面找一个心仪的男士,然后安排藤篮与他见面。



藤兰很快选中了令狐冲。负责人告诉藤篮:“令狐冲明天和任女士有约,你们只能后天见面了“。藤篮说:“好的,我正好准备准备”。



结果两天过后,负责人告诉藤篮:因为昨天任女士有事,没能和令狐冲见面,所以我们安排今天任女士与令狐冲见面,你到明天再约吧!



藤篮很生气:既然昨天任女士有事,为啥不安排让我昨天和令狐冲见面呢?



负责人说:不行!俺们讲究的是先来后到!因为任女士先约的令狐冲,甭管怎么着,你都得排在任女士后面



藤篮很生气!于是来到了异步相亲机构。



3异步相亲机构:这个机构的负责人则很灵活:



一般情况下遵从先来后到的理念,特殊情况特殊对待。



藤篮很喜欢这个负责人的理念,于是和这里的负责人携手一生.......



4再来总结一下:Javascript语言的执行环境是"单线程",所谓"单线程",就是指一次只能完成一件任务。



同步就是:后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的



异步就是:一个任务可能有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而执行回调函数,



后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的



3上面异步中讲到,一个任务可能有一个或多个回调函数,假如a任务有a1,a2,a3这三个回调函数,b也需要a1,a2,a3这三个回调函数。我们需要这样做:



ViewCode

4上面的代码很麻烦是不是?Callback就是来解决这种麻烦!!



Callbacks如何解决这种麻烦?

先来说一下大体思路:



1首先我们每次调用Callbacks(),都会返回一个callbacks对象,这个对象有一个仅仅只有自己才能访问的数组(就是用了闭包呗)



2这个数组就是用来存储回调函数的。



3callbacks对象有许多方法,可以对数组进行操作,比如说:添加(add),清空(empty),删除(remove),运行数组中的所有回调函数(fire).......



4那么,我们上面---为什么搞了个Callbacks函数?---的第三条任务a,任务b可以如下改写:



ViewCode

升华:



Callbacks可不仅仅只实现了这些,他还提供了很多参数:vara=Callbacks(''oncememoryuniquestopOnFalse'')



once:如果创建Callbacks时加入该参数,则运行数组中的所有回调函数之后,也就是fire()之后,会清空数组。



memory:会保存上一次运行fire(args)时的参数args,每当添加一个新的回调函数到数组中,会立即使用args作为参数调用新加的函数一次



unique:确保数组中的回调函数互不相同



stopOnFalse:当运行fire()时,若某个回调函数返回false,则立即终止余下的回调函数执行



尽管这些参数可能有些初看没吊用,长时间使用jQuery之后发现:确实没吊用。但是不得要说,jQuery的开发人员真的很细致!



源码讲解:



复制代码

define([

"./core",

"./var/rnotwhite"

],function(jQuery,rnotwhite){



//StringtoObjectoptionsformatcache

varoptionsCache={};



//ConvertString-formattedoptionsintoObject-formattedonesandstoreincache

/

如果:vara=$.Callback(''oncememory'')

则optionsCache中会有这么一项:"oncememory":{memory:true,once:true}

/

functioncreateOptions(options){

varobject=optionsCache[options]={};

jQuery.each(options.match(rnotwhite)||[],function(_,flag){

object[flag]=true;

});

returnobject;

}



/

Createacallbacklistusingthefollowingparameters:



options:anoptionallistofspace-separatedoptionsthatwillchangehow

thecallbacklistbehavesoramoretraditionaloptionobject



Bydefaultacallbacklistwillactlikeaneventcallbacklistandcanbe

"fired"multipletimes.



Possibleoptions:



once:willensurethecallbacklistcanonlybefiredonce(likeaDeferred)



memory:willkeeptrackofpreviousvaluesandwillcallanycallbackadded

afterthelisthasbeenfiredrightawaywiththelatest"memorized"

values(likeawww.wang027.comDeferred)



unique:willensureacallbackcanonlybeaddedonce(noduplicateinthelist)



stopOnFalse:interruptcallingswhenacallbackreturnsfalse



/

jQuery.Callbacks=function(options){



//ConvertoptionsfromString-formattedtoObject-formattedifneeded

//(wecheckincachefirst)

options=typeofoptions==="string"?

(optionsCache[options]||createOptions(options)):

jQuery.extend({},options);



var//Lastfirevalue(fornon-forgettablelists)

memory,

//Flagtoknowiflistwasalreadyfiredlist是否已经被fire函数调用过

fired,

//Flagtoknowiflistiscurrentlyfiring当前是否正在调用fire函数

firing,

//Firstcallbacktofire(usedinternallybyaddandfireWith)第一个被执行的回调函数在list的位置

firingStart,

//Endoftheloopwhenfiringfire函数要运行的回调函数的个数

firingLength,

//Indexofcurrentlyfiringcallback(modifiedbyremoveifneeded)当前正在执行的回调函数的索引

firingIndex,

//回调函数数组

list=[],

//Stackoffirecallsforrepeatablelists可重复的回调函数栈。我们可能会短时间内执行多次fire(),若当前的fire()正在迭代执行回调函数,而紧接着又执行了一次fire()时,会将下一次的fire()参数等保存至stack中,等待当前的fire()执行完成后,将stack中的fire()进行执行

stack=!options.once&&[],

//Firecallbacks

fire=function(data){

//data[0]是一个对象,data[1]则是回调函数的参数

memory=options.memory&&data;//很精妙,仔细体会一下这句代码,如果调用Calbacks时传入了memory,则memory=data,否则memory=false

fired=true;//在调用本函数时,将fired状态进行修改

firingIndex=firingStart||0;

firingStart=0;

firingLength=list.length;

firing=true;//迭代回调函数之前,将firing状态进行修改

for(;list&&firingIndex
if(list[firingInwww.baiyuewang.netdex].apply(data[0],data[1])===false&&

options.stopOnFalse){//运行回调函数的同时,检测回调函数是否返回false,若返回false,且调用Callbacks时传入stopOnFalse参数,则终止迭代



memory=false;//Topreventfurthercallsusingadd既然终止迭代了,那么之后添加的回调函数都不应该被调用,将memory设置为false

break;

}

}

firing=false;//迭代回调函数完成后,将firing状态进行修改

if(list){

if(stack){//没有使用once参数

if(stack.length){

fire(stack.shift());

}

}elseif(memory){//使用了oncememory参数,则在迭代完回调函数之后清空list

list=[];

}else{//其他

self.disable();

}

}

},

//ActualCallbacksobject

self={

//将一个新的回调函数添加至list

add:function(){

if(list){

//First,wesavethecurrentlength首先,我们将当前的长度进行保存

varstart=list.length;

(functionadd(args){//自执行函数

jQuery.each(args,function(_,arg){

vartype=jQuery.type(arg);

if(type==="function"){

if(!options.unique||!self.has(arg)){

list.push(arg);//若参数中的元素为函数且(无unique参数或者list中没有该函数),则将该函数添加至list末尾

}

}elseif(arg&&arg.length&&type!=="string"){//arg的长度不为0且每项的类型不为字符串,也就是args为这种情况:[[fun1,fun2...],[fun3,fun4]](不仅限于这种情况)

//Inspectrecursively

add(arg);

}

});

})(arguments);

//Doweneedtoaddthecallbackstothe

//currentfiringbatch?

//当Callback中的firingLength变为动态的!也就是:只要我们向list中添加了一个新的回调函数,即使在fire()运行过程中,改变也能立即体现出来

if(firing){

firingLength=list.length;

//Withmemory,ifwe''renotfiringthen

//weshouldcallrightaway

}elseif(memory){//如果当前没有执行回调函数,且存在memory参数,则执行新添加的回调函数

firingStart=start;

fire(memory);

}

}

returnthis;

},

//Removeacallbackfromthelist将一个回调函数从list中移除

remove:function(){

if(list){

jQuery.each(arguments,function(_,arg){

varindex;

while((index=jQuery.inArray(arg,list,index))>-1){

list.splice(index,1);

//Handlefiringindexes

if(firing){

if(index<=firingLength){

firingLength--;

}

if(index<=firingIndex){

firingIndex--;

}

}

}

});

}

returnthis;

},

//Checkifagivencallbackisinthelist.

//Ifnoargumentisgiven,returnwhetherornotlisthascallbacksattached.

has:function(fn){

returnfn?jQuery.inArray(fn,list)>-1:!!(list&&list.length);

},

//Removeallcallbacksfromthelist清空数组

empty:function(){

list=[];

firingLength=0;

returnthis;

},

//Havethelistdonothinganymore使用了这个方法,则意味着该回调对象失效了。

disable:function(){

list=stack=memory=undefined;

returnthis;

},

//Isitdisabled?

disabled:function(){

return!list;

},

//Lockthelistinitscurrentstate给数组上锁

lock:function(){

stack=undefined;

if(!memory){

self.disable();

}

returnthis;

},

//Isitlocked?

locked:function(){

return!stack;

},

//Callallcallbackswiththegivencontextandarguments

//使用传入的context作为当前函数的执行上下文

fireWith:function(context,args){

if(list&&(!fired||stack)){

args=args||[];

args=[context,args.slice?args.slice():args];

if(firing){

stack.push(args);//如果当前正在迭代执行回调函数,则将新的fire参数推入stack中

}else{

fire(args);

}

}

returnthis;

},

//Callallthecallbackswiththegivenarguments

fire:function(){

self.fireWith(this,arguments);

returnthis;

},

//Toknowifthecallbackshavealreadybeencalledatleastonce

//用来确定当前callback对象是否被fire()过

fired:function(){

return!!fired;

}

};



returnself;

};



returnjQuery;

});

献花(0)
+1
(本文系thedust79首藏)