jQuery原始碼學習:非同步操作--Callbacks
jQuery封裝了很多關於非同步操作的方法,比如$.ajax
$.Deferred
等等,這些方法都是以Callbacks為基礎開發的,Callbacks一個多用途的回撥列表物件,提供了強大的的方式來管理回撥函式列表,他可以管理一個列表,在你需要執行時候觸發,觸發時機由呼叫fire方法決定
瞭解API
var cb = $.Callbacks(); cb.add(function(){ console.log('first function'); }) cb.add(function () { console.log('second function'); }) cb.fire(); // first function // second function 複製程式碼
$.Callbacks()返回一個Callbacks物件,你可以呼叫該物件的方法對函式列表進行操作,要實現這樣的功能很簡單,下面是一個簡易的實現
function Callbakcs () { var list = [] return { add: function (func) { list.push(func); }, fire: function () { var args = [].slice.call(arguments); list.forEach(function(item) { item.apply(null, arguments); }) } } } 複製程式碼
同樣的功能,只不過jQuery支援了不同的呼叫方式
引數
在呼叫$.Callbacks的時候,我們可以傳入一個引數用來改變回調列表的行為
- once 確保改列表只執行一次,呼叫fire過後便失效
- unique 一個回撥只會被新增一次,不會重複新增
- stopOnFalse 在某一個回撥返回false時候立即停止執行後面函式
- memory 快取上一次fire的引數,下一次add的時候執行用上一次的引數呼叫
// 支援兩種傳參方式,一種物件類的,一種字串類的,可組合使用,傳入字串的時候會對字串進行處理轉換為物件 var cb = $.Callback("once unique"); var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); function createOptions( options ) { var object = {}; jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { object[ flag ] = true; } ); return object; } // 函式內部儲存了一個options物件,最後會是類似{once: true, unique: true}這樣的物件 複製程式碼
變數說明
在函式內部我們還能看到其他一些變數
var //標記是否正在觸發 firing, //快取的上一次呼叫時候的引數和this指向 memory, // 標記是否已經呼叫過fire fired, // 防止呼叫的標記 locked, // 回撥列表 list = [], // 引數和this指向的列表 queue = [], // 標記當前執行到哪個回撥 firingIndex = -1, // 執行回撥列表 fire = function () {} // 暴露的物件 self = {} 複製程式碼
add
add方法主要會判斷是否是unique、memory模式、unique遇到相同的回撥函式回過濾掉,memory會檢查fired是否為true,如果是會將memory裡面的引數新增到queue佇列中,最後呼叫fire
if ( memory && !firing ) { firingIndex = list.length - 1; queue.push( memory ); } if ( memory && !firing ) { firingIndex = list.length - 1; queue.push( memory ); } ( function add( args ) { jQuery.each( args, function( _, arg ) { if ( isFunction( arg ) ) { if ( !options.unique || !self.has( arg ) ) { list.push( arg ); } } else if ( arg && arg.length && toType( arg ) !== "string" ) { // Inspect recursively add( arg ); } } ); } )( arguments ); 複製程式碼
新增的是否用到了遞迴,因為支援了傳入陣列的方法
fire
self裡面會呼叫fireWith方法,我們在呼叫fire的時候實際上是呼叫的這個方法
fire: function() { self.fireWith( this, arguments ); return this; }, fireWith: function( context, args ) { if ( !locked ) { args = args || []; args = [ context, args.slice ? args.slice() : args ]; // 降引數和this物件快取到queue裡面 queue.push( args ); if ( !firing ) { fire(); } } return this; }, 複製程式碼
真正的fire會遍歷取出queue裡面的引數,快取到memory裡面,如果不是memory模式則會在後面清除掉,遍歷list,用memory裡的引數呼叫各個方法
for ( ; queue.length; firingIndex = -1 ) { memory = queue.shift(); while ( ++firingIndex < list.length ) { // 如果傳入stopOnFalse,且返回false,立即停止遍歷 if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && options.stopOnFalse ) { firingIndex = list.length; memory = false; } } } 複製程式碼
Callback部分的程式碼比較簡單,當然這部分都是同步邏輯,callback是Deffered的基礎,所以需要先明白一下原理