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