jQuery-1.9.1源码分析系列(五) 回调对象
jQuery.Callbacks()提供的回調(diào)函數(shù)隊(duì)列管理本來(lái)是延時(shí)回調(diào)處理的一部分,但是后面將其獨(dú)立出來(lái)作為一個(gè)模塊。jQuery就是這樣,各個(gè)模塊間的代碼耦合度是處理的比較好的,值得學(xué)習(xí)。雖然是從延時(shí)回調(diào)處理中獨(dú)立出來(lái)的,但是它的功能非常強(qiáng)大,提供了一種強(qiáng)大的方法來(lái)管理回調(diào)函數(shù)隊(duì)列。
大家都明白封裝函數(shù)的目的:去耦合與簡(jiǎn)化操作。
通常情況下函數(shù)隊(duì)列的處理方式
//執(zhí)行函數(shù) function runList(arr){for(var i = 0; i < arr.length; i++){arr[i](); }arr.length = 0; }var list = []; //添加函數(shù)隊(duì)列 list[list.length] = function(){alert(1)}; list[list.length] = function(){alert(2)};
list[list.length] = function(){alert(3)}; //執(zhí)行
runList(list);//三個(gè)函數(shù)順序執(zhí)行
使用$.callbacks封裝以后的處理為
var callbacks = $.Callbacks("unique");callbacks.add( function(){alert(1)} ); callbacks.add( function(){alert(2)} ); callbacks.add( function(){alert(3)} ); //執(zhí)行 callbacks.fire();//三個(gè)函數(shù)順序執(zhí)行干凈了很多。而且代碼可讀性比最開(kāi)始的那個(gè)要好很多。list[list.length]神馬的最討厭了。還有主要的是$.callbacks有四個(gè)屬性可以組合,這個(gè)組合可就很強(qiáng)大了。
a. Callbacks的四個(gè)可設(shè)置的屬性分析
?
once: 確保這個(gè)回調(diào)列表只執(zhí)行( .fire() )一次(像一個(gè)遞延 Deferred).
?????? 設(shè)置“once”在執(zhí)行第一次fire后會(huì)直接禁用該Callbacks(fire函數(shù)代碼段else {self.disable();})
var f1 = function(value) { console.log(value); }; var callbacks = $.Callbacks('once');callbacks.add(f1);//無(wú)執(zhí)行結(jié)果,添加一個(gè)回調(diào) callbacks.fire(1);//執(zhí)行結(jié)果1。清除回調(diào)列表 callbacks.add(f1);//沒(méi)有添加回調(diào)直接返回 callbacks.fire(2);//無(wú)執(zhí)行結(jié)果 callbacks.add(f1);//沒(méi)有添加回調(diào)直接返回 callbacks.fire(3);//無(wú)執(zhí)行結(jié)果?
memory:?保持以前的值(參數(shù)),將函數(shù)添加到這個(gè)列表的后面,并使用先前保存的參數(shù)立即執(zhí)行該函數(shù)。 內(nèi)部變量會(huì)保存上次執(zhí)行的場(chǎng)景。
他有一個(gè)特點(diǎn),就是在第一次fire之前使用add添加的回調(diào)都不會(huì)馬上執(zhí)行,只有調(diào)用了一次fire之后使用add添加的回調(diào)會(huì)馬上執(zhí)行。該設(shè)置本身不會(huì)清除之前的回調(diào)列表。
需要注意的是每次add內(nèi)部執(zhí)行fire函數(shù)都會(huì)將firingStart置為0,只有下次add的時(shí)候會(huì)從新設(shè)置firingStart的值。
eg:
var f1 = function(value) { console.log(value); }; var callbacks = $.Callbacks("memory");callbacks.add( fn1 );//無(wú)執(zhí)行結(jié)果 callbacks.fire( "1" );//執(zhí)行結(jié)果1。保存場(chǎng)景參數(shù)1 callbacks.add( fn1 );//執(zhí)行結(jié)果1。使用上次保存的場(chǎng)景參數(shù)1 callbacks.fire( "2" );//執(zhí)行結(jié)果2,2。保存場(chǎng)景參數(shù)2 callbacks.add( fn1 );//執(zhí)行結(jié)果2。使用上次保存的場(chǎng)景參數(shù)2 callbacks.fire( "3" );//執(zhí)行結(jié)果3,3,3。保存場(chǎng)景參數(shù)3 callbacks.add( fn1 );//執(zhí)行結(jié)果3。使用上次保存的場(chǎng)景參數(shù)3 callbacks.fire( "4" );//執(zhí)行結(jié)果4,4,4,4。保存場(chǎng)景參數(shù)4組合使用,組合使用中間使用空格隔開(kāi)
設(shè)置“once memory”, options.once=options.memory=true。在執(zhí)行第一次fire后會(huì)把回到列表清空,而且之后每次add馬上執(zhí)行后頁(yè)同樣會(huì)把回調(diào)列表清空(fire函數(shù)代碼段else if ( memory ) {list = [];})。
?????? eg:
var f1 = function(value) { console.log(value); }; var callbacks = $.Callbacks('once memory');callbacks.add(f1);//無(wú)執(zhí)行結(jié)果,添加一個(gè)回調(diào) callbacks.fire(1);//執(zhí)行結(jié)果1。清除回調(diào)列表,保存場(chǎng)景參數(shù)1 callbacks.add(f1);//添加一個(gè)回調(diào)并執(zhí)行結(jié)果1,使用上次保存的場(chǎng)景參數(shù)。清除回調(diào)列表 callbacks.fire(2);//無(wú)執(zhí)行結(jié)果 callbacks.add(f1);//添加一個(gè)回調(diào)并執(zhí)行結(jié)果1,使用上次保存的場(chǎng)景參數(shù)。清除回調(diào)列表 callbacks.fire(3);//無(wú)執(zhí)行結(jié)果兩個(gè)設(shè)置之間用空格,不支持其他符號(hào),比如設(shè)置“once,memory”等同于沒(méi)有設(shè)置。??????
eg:
var f1 = function(value) { console.log(value); }; var callbacks = $.Callbacks('once,memory');callbacks.add(f1);//無(wú)執(zhí)行結(jié)果,添加一個(gè)回調(diào) callbacks.fire(1);//執(zhí)行結(jié)果1 callbacks.add(f1);//添加一個(gè)回調(diào) callbacks.fire(2);//執(zhí)行結(jié)果2,2 callbacks.add(f1);//添加一個(gè)回調(diào) callbacks.fire(3);//執(zhí)行結(jié)果3,3,3 callbacks.add(f1);//添加一個(gè)回調(diào) callbacks.fire(4);//執(zhí)行結(jié)果4,4,4,4?
unique: 確保一次只能添加一個(gè)回調(diào)(所以在列表中沒(méi)有重復(fù)的回調(diào)).
?
stopOnFalse: 當(dāng)一個(gè)回調(diào)返回false 時(shí)中斷調(diào)用
?????? 當(dāng)有一個(gè)回調(diào)返回false的時(shí)候,會(huì)設(shè)置memory為false。導(dǎo)致memory失去作用(后續(xù)add的函數(shù)不會(huì)馬上執(zhí)行,當(dāng)然先前memory保證了前面執(zhí)行過(guò)得函數(shù)不再執(zhí)行這也條也就不起作用了。下次fire會(huì)從回調(diào)列表的第一個(gè)開(kāi)始執(zhí)行)。
?
b. 整體結(jié)構(gòu)
使用緩存是jQuery中最常見(jiàn)的技巧。$.Callbacks中也不例外。主要是緩存Callbacks中遇到的選項(xiàng)(字符串)。
// 使用過(guò)的選項(xiàng)緩存 var optionsCache = {};// 新增和緩存回調(diào)設(shè)置于optionsCache中 function createOptions( options ) {var object = optionsCache[ options ] = {};jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {object[ flag ] = true;});return object; }jQuery.Callbacks = function( options ) {// 盡可能讀取緩存,沒(méi)有則新增緩存options = typeof options === "string" ?( optionsCache[ options ] || createOptions( options ) ) :jQuery.extend( {}, options );var // 回調(diào)列表正在執(zhí)行(為true的時(shí)候)的標(biāo)志 firing,// 最后執(zhí)行的值(為memory選項(xiàng)下保存) memory,// 回調(diào)已經(jīng)被執(zhí)行過(guò)的標(biāo)志 fired,// 循環(huán)執(zhí)行回調(diào)列表的結(jié)束位置 firingLength,// 當(dāng)前真正執(zhí)行的回調(diào)的索引值 (執(zhí)行下個(gè)回調(diào)的時(shí)候回更改【如果必要的話】) firingIndex,// 循環(huán)執(zhí)行回調(diào)列表的開(kāi)始位置(在函數(shù)add和fireWith中使用) firingStart,// 回調(diào)列表list = [],// Stack記錄要重復(fù)執(zhí)行的回調(diào)列表stack = !options.once && [],// data數(shù)組一般第一個(gè)元素是上下文環(huán)境,第二個(gè)元素是參數(shù)//執(zhí)行回調(diào)列表fire = function( data ) {…},// 回調(diào)對(duì)象self = {// 添加回調(diào)add: function() {…},// 移除回調(diào)remove: function() {…},...// 給定 context 和 arguments執(zhí)行所有回調(diào)fireWith: function( context, args ) {args = args || [];//組裝args,第一個(gè)元素為上下文環(huán)境,第二個(gè)元素為參數(shù)列表args = [ context, args.slice ? args.slice() : args ];//有l(wèi)ist且函數(shù)列表沒(méi)有被執(zhí)行過(guò)或者存在要循環(huán)執(zhí)行的函數(shù)列表if ( list && ( !fired || stack ) ) {//如果正在fire,則把函數(shù)場(chǎng)景記錄在stack中if ( firing ) {stack.push( args );//否則,至此那個(gè)fire} else {fire( args );}}return this;},// 使用給定的arguments執(zhí)行所有回調(diào)fire: function() {self.fireWith( this, arguments );return this;},...};return self; };
下面分析兩個(gè)最重要的兩個(gè)函數(shù),添加回調(diào)函數(shù)add和執(zhí)行回調(diào)函數(shù)fire
c. add:添加回調(diào)
添加回調(diào)函數(shù)比較簡(jiǎn)單,針對(duì)可能傳遞的值(函數(shù)或者函數(shù)數(shù)組)將回調(diào)添加到回調(diào)列表中即可,這里使用了一個(gè)閉包,使用了外部變量list。
(function add( args ) {jQuery.each( args, function( _, arg ) {var type = jQuery.type( arg );if ( type === "function" ) {//當(dāng)$.Callbacks('unique')時(shí),保證列表里面不會(huì)出現(xiàn)重復(fù)的回調(diào)if ( !options.unique || !self.has( arg ) ) {list.push( arg );}//如果是數(shù)組則遞歸添加} else if ( arg && arg.length && type !== "string" ) {add( arg );}});})( arguments );但是這里需要對(duì)用戶(hù)初始化設(shè)置的屬性做一些特殊的處理。
如果列表沒(méi)有定義或null(一般只有在用戶(hù)設(shè)置once且執(zhí)行過(guò)一次后list才會(huì)白置為未定義),直接返回list
//如果列表沒(méi)有定義或null(一般只有在用戶(hù)設(shè)置once且執(zhí)行過(guò)一次后list才會(huì)白置為未定義)if ( list ) {...}return this;當(dāng)有回調(diào)真正執(zhí)行的時(shí)候,需要重新設(shè)定回調(diào)列表的結(jié)束位置firingLength,使后續(xù)添加的函數(shù)也會(huì)執(zhí)行。實(shí)際上這個(gè)功能很受爭(zhēng)議,不過(guò)正常情況一般不會(huì)出現(xiàn)添加函數(shù)的時(shí)候正在執(zhí)行某個(gè)回調(diào)。
還有一個(gè)比較重要的判斷:對(duì)于設(shè)置了'memory'選項(xiàng)并fire過(guò)了回調(diào)列表,并且沒(méi)有還在等待中的回調(diào)要fire,則應(yīng)當(dāng)馬上執(zhí)行新添加的回調(diào)(執(zhí)行fire(memory))
// 如果正在fire,則設(shè)定要執(zhí)行結(jié)束的點(diǎn)firingLength,使后續(xù)添加的函數(shù)最后不會(huì)執(zhí)行if ( firing ) {firingLength = list.length;// 對(duì)于memory(設(shè)置了'memory' option并fire過(guò)了,memory才能通過(guò)該else if語(yǔ)句),//如果沒(méi)有回調(diào)真正fire,應(yīng)當(dāng)馬上執(zhí)行fire(memory)。} else if ( memory ) {//這里保證了前面執(zhí)行過(guò)得函數(shù)不再執(zhí)行firingStart = start;fire( memory );}完整的源碼如下
add: function() {//如果列表沒(méi)有定義或null(一般只有在用戶(hù)設(shè)置once且執(zhí)行過(guò)一次后list才會(huì)白置為未定義)if ( list ) {// 保存當(dāng)前l(fā)ist長(zhǎng)度,為memory處理備用var start = list.length;(function add( args ) {jQuery.each( args, function( _, arg ) {var type = jQuery.type( arg );if ( type === "function" ) {//當(dāng)$.Callbacks('unique')時(shí),保證列表里面不會(huì)出現(xiàn)重復(fù)的回調(diào)if ( !options.unique || !self.has( arg ) ) {list.push( arg );}//如果是數(shù)組則遞歸添加} else if ( arg && arg.length && type !== "string" ) {add( arg );}});})( arguments );// 如果正在fire,則設(shè)定要執(zhí)行結(jié)束的點(diǎn)firingLength,使后續(xù)添加的函數(shù)最后執(zhí)行if ( firing ) {firingLength = list.length;// 對(duì)于memory(設(shè)置了'memory' option并fire過(guò)了,memory才能通過(guò)該else if語(yǔ)句),//如果我們后續(xù)沒(méi)有fire,應(yīng)當(dāng)馬上執(zhí)行fire(memory)。} else if ( memory ) {//這里保證了前面執(zhí)行過(guò)得函數(shù)不再執(zhí)行firingStart = start;fire( memory );}}return this; } View Code
d. fire函數(shù)詳解
該函數(shù)執(zhí)行回調(diào),最終執(zhí)行代碼段為
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {memory = false; // 阻止未來(lái)可能由于add所產(chǎn)生的回調(diào)break; }?
fire = function( data ) { //有memory才給memory賦值當(dāng)前場(chǎng)景datamemory = options.memory && data;fired = true;firingIndex = firingStart || 0; //每次fire后都會(huì)重置成0,下次$.callbacks.fire調(diào)用都會(huì)從0開(kāi)始。當(dāng)然設(shè)置為‘memory’使用add函數(shù)內(nèi)部fire會(huì)設(shè)置firingStart的值導(dǎo)致回調(diào)函數(shù)列表執(zhí)行起始位置更改firingStart = 0;firingLength = list.length;firing = true;//函數(shù)開(kāi)始執(zhí)行從firingStart到firingLength的所有函數(shù) for ( ; list && firingIndex < firingLength; firingIndex++ ) { //執(zhí)行firingIndex對(duì)應(yīng)的函數(shù),如果設(shè)置是遇到false返回就停止,則設(shè)置memory,阻止后續(xù)函數(shù)執(zhí)行 if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {memory = false; // 阻止未來(lái)可能由于add所產(chǎn)生的回調(diào) break;}} //標(biāo)記回調(diào)結(jié)束firing = false;//如果列表存在 if ( list ) { //如果堆棧存在(一般沒(méi)有設(shè)置once的時(shí)候都進(jìn)入該分支) if ( stack ) { //如果堆棧不為空 if ( stack.length ) { //執(zhí)行stack中第一個(gè)元素fire( stack.shift() );} //如果有記憶,則清空列表(在設(shè)置為once且memory的時(shí)候會(huì)進(jìn)入到此分支)} else if ( memory ) {list = []; //禁用回調(diào),該callbacks將不可用,將list/stack/memory都設(shè)為未定義} else {self.disable();}} },
真正重要的是執(zhí)行完成回調(diào)以后的處理
//如果列表存在if ( list ) {//如果堆棧存在(一般沒(méi)有設(shè)置once的時(shí)候都進(jìn)入該分支)if ( stack ) {//如果堆棧不為空if ( stack.length ) {//執(zhí)行stack中第一個(gè)元素 fire( stack.shift() );}//如果有記憶,則清空列表(在設(shè)置為once且memory的時(shí)候會(huì)進(jìn)入到此分支)} else if ( memory ) {list = [];//禁用回調(diào),該callbacks將不可用,將list/stack/memory都設(shè)為未定義} else {self.disable();}} View Code首先看最外層的判斷
if ( list ){... }?
什么時(shí)候會(huì)進(jìn)不了這個(gè)分支呢?唯有當(dāng)self.disable()被調(diào)用的時(shí)候,下一次fire就進(jìn)入不了這個(gè)分支。查看self.disable源碼
disable: function() {list = stack = memory = undefined;return this;}根據(jù)里面的判斷唯有當(dāng)options選項(xiàng)有once,并且選項(xiàng)中沒(méi)有memory或選項(xiàng)中有stopOnFalse且執(zhí)行的回調(diào)返回false。這個(gè)時(shí)候回進(jìn)入到里面的分支直接將整個(gè)回調(diào)禁用掉。
//禁用回調(diào),該callbacks將不可用,將list/stack/memory都設(shè)為未定義} else {self.disable();}第一個(gè)內(nèi)部分支if ( stack )主要是選項(xiàng)中沒(méi)有once就進(jìn)入。
第二個(gè)內(nèi)部分支只有在選項(xiàng)至少有once和memory的時(shí)候才會(huì)進(jìn)入。當(dāng)然,如果還有stopOnFalse且執(zhí)行的回調(diào)返回false會(huì)進(jìn)入到第三個(gè)分支。
//如果有記憶,則清空列表(在設(shè)置為once且memory的時(shí)候會(huì)進(jìn)入到此分支)} else if ( memory ) {?
好了,這個(gè)jQuery.Callbacks就到這里。需要注意的就是多個(gè)選項(xiàng)混合使用要特別小心。
?
如果覺(jué)得本文不錯(cuò),請(qǐng)點(diǎn)擊右下方【推薦】!
?
轉(zhuǎn)載于:https://www.cnblogs.com/chuaWeb/p/jQuery-1-9-1-Callbacks.html
總結(jié)
以上是生活随笔為你收集整理的jQuery-1.9.1源码分析系列(五) 回调对象的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 400元档性能天花板!当贝盒子H3S发布
- 下一篇: LoaderManager使用详解(二)