国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

談談jQuery之Deferred源碼剖析

2019-11-19 18:24:15
字體:
來源:轉載
供稿:網友

一、前言

大約在夏季,我們談過ES6的Promise,其實在ES6前jQuery早就有了Promise,也就是我們所知道的Deferred對象,宗旨當然也和ES6的Promise一樣,通過鏈式調用,避免層層嵌套,如下:

//jquery版本大于1.8function runAsync(){  var def = $.Deferred();  setTimeout(function(){    console.log('I am done');    def.resolve('whatever');  }, 1000);  return def;}runAsync().then(function(msg){  console.log(msg);//=>打印'whatever'}).done(function(msg){  console.log(msg);//=>打印'undefined'});

注:從jQuery1.8版本開始,then方法會返回一個新的受限制的deferred對象,即deferred.promise()―后續源碼解讀中我們會更加全面地了解到。因此,上述代碼done中會打印'undefined'。

好了,通過上述示例代碼,短暫的回顧了jQuery的Deferred使用后,我們一起來看看jQuery是怎么實現Deferred,當然解讀jQuery的版本是大于1.8。

二、jQuery之Deferred源碼剖析

整體架構,如下:

jQuery.extend( {  Deferred: function( func ) {    var tuples = [        // action, add listener, listener list, final state        [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],        [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],        [ "notify", "progress", jQuery.Callbacks( "memory" ) ]      ],      state = "pending",      promise = {        state: function() {...},        always: function() {...},        then: function() {...},        promise: function( obj ) {...}      },      deferred = {};    // Keep pipe for back-compat    promise.pipe = promise.then;    // Add list-specific methods    jQuery.each( tuples, function( i, tuple ) {} );    // Make the deferred a promise    promise.promise( deferred );    // Call given func if any    if ( func ) {      func.call( deferred, deferred );    }    // All done!    return deferred;  }}

整體架構上,如果你了解設計模式中的工廠模式,那么不難看出,jQuery.Deferred就是一個工廠,每次執行jQuery.Deferred時,都會返回一個加工好的deferred對象。

接下來,我們再一步一步剖析上述代碼。

首先,是數組tuples:

tuples = [  // action, add listener, listener list, final state  [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],  [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],  [ "notify", "progress", jQuery.Callbacks( "memory" ) ]]

tuples一開始就為我們預先定義了三種狀態―‘resolved'、'rejected'以及'pending',以及它們所對應的一系列值和操作,值得注意的是每種狀態中,都調用了一個jQuery.Callbacks方法,如下:

它是個什么玩意兒?

jQuery.Callbacks = function( options ) {  // Convert options from String-formatted to Object-formatted if needed  // (we check in cache first)  options = typeof options === "string" ?    createOptions( options ) :    jQuery.extend( {}, options );  var // Flag to know if list is currently firing    firing,    // Last fire value for non-forgettable lists    memory,    // Flag to know if list was already fired    fired,    // Flag to prevent firing    locked,    // Actual callback list    list = [],    // Queue of execution data for repeatable lists    queue = [],    // Index of currently firing callback (modified by add/remove as needed)    firingIndex = -1,    // Fire callbacks    fire = function() {      // Enforce single-firing      locked = options.once;      // Execute callbacks for all pending executions,      // respecting firingIndex overrides and runtime changes      fired = firing = true;      for ( ; queue.length; firingIndex = -1 ) {        memory = queue.shift();        while ( ++firingIndex < list.length ) {          // Run callback and check for early termination          if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&            options.stopOnFalse ) {            // Jump to end and forget the data so .add doesn't re-fire            firingIndex = list.length;            memory = false;          }        }      }      // Forget the data if we're done with it      if ( !options.memory ) {        memory = false;      }      firing = false;      // Clean up if we're done firing for good      if ( locked ) {        // Keep an empty list if we have data for future add calls        if ( memory ) {          list = [];        // Otherwise, this object is spent        } else {          list = "";        }      }    },    // Actual Callbacks object    self = {      // Add a callback or a collection of callbacks to the list      add: function() {        if ( list ) {          // If we have memory from a past run, we should fire after adding          if ( memory && !firing ) {            firingIndex = list.length - 1;            queue.push( memory );          }          ( function add( args ) {            jQuery.each( args, function( _, arg ) {              if ( jQuery.isFunction( arg ) ) {                if ( !options.unique || !self.has( arg ) ) {                  list.push( arg );                }              } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {                // Inspect recursively                add( arg );              }            } );          } )( arguments );          if ( memory && !firing ) {            fire();          }        }        return this;      },      // Remove a callback from the list      remove: function() {        jQuery.each( arguments, function( _, arg ) {          var index;          while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {            list.splice( index, 1 );            // Handle firing indexes            if ( index <= firingIndex ) {              firingIndex--;            }          }        } );        return this;      },      // Check if a given callback is in the list.      // If no argument is given, return whether or not list has callbacks attached.      has: function( fn ) {        return fn ?          jQuery.inArray( fn, list ) > -1 :          list.length > 0;      },      // Remove all callbacks from the list      empty: function() {        if ( list ) {          list = [];        }        return this;      },      // Disable .fire and .add      // Abort any current/pending executions      // Clear all callbacks and values      disable: function() {        locked = queue = [];        list = memory = "";        return this;      },      disabled: function() {        return !list;      },      // Disable .fire      // Also disable .add unless we have memory (since it would have no effect)      // Abort any pending executions      lock: function() {        locked = true;        if ( !memory ) {          self.disable();        }        return this;      },      locked: function() {        return !!locked;      },      // Call all callbacks with the given context and arguments      fireWith: function( context, args ) {        if ( !locked ) {          args = args || [];          args = [ context, args.slice ? args.slice() : args ];          queue.push( args );          if ( !firing ) {            fire();          }        }        return this;      },      // Call all the callbacks with the given arguments      fire: function() {        self.fireWith( this, arguments );        return this;      },      // To know if the callbacks have already been called at least once      fired: function() {        return !!fired;      }    };  return self;};

 

細細品味了上述jQuery.Callbacks源碼,如果你了解設計模式中的發布訂閱者模式,不難發現,就是一個”自定義事件”嘛

所以,我們精簡jQuery.Callbacks后,核心代碼如下:

jQuery.Callbacks = function(){  var list = [],    self = {      add: function(){/*添加元素到list*/},      remove: function(){/*從list移除指定元素*/},      fire: function(){/*遍歷list并觸發每次元素*/}    };  return self;}

一目了然,我們每執行一次jQuery.Callbacks方法,它就會返回一個獨立的自定義事件對象。在tuples每個狀態中執行一次jQuery.Callbacks,也就豁然開朗了―為每個狀態提供一個獨立的空間來添加、刪除以及觸發事件。

好了,關于變量tuples,我們就算大致解讀完了。

state就是deferred對象的狀態值嘛,我們可以通過deferred.state方法獲取(稍后會見到)。

promise就是一個擁有state、always、then、promise方法的對象,每個方法詳解如下:

promise = {  state: function() {//返回狀態值    return state;  },  always: function() {//不管成功還是失敗,最終都會執行該方法    deferred.done( arguments ).fail( arguments );    return this;  },  then: function( /* fnDone, fnFail, fnProgress */ ) {...},//重頭戲,稍后會詳講  promise: function( obj ) {//擴展promise,如不久我們會看見的promise.promise( deferred );    return obj != null ? jQuery.extend( obj, promise ) : promise;  }}

隨后聲明的一個空對象deferred。

promise.pipe=promise.then,就不累贅了,下面我們來看看jQuery.each(tuples, function(i, tuple){…})都干了什么,源碼如下:

/*tuples = [  [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],  [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],  [ "notify", "progress", jQuery.Callbacks( "memory" ) ]]*/jQuery.each( tuples, function( i, tuple ) {  var list = tuple[ 2 ],    stateString = tuple[ 3 ];  // promise[ done | fail | progress ] = list.add  promise[ tuple[ 1 ] ] = list.add;  // Handle state  if ( stateString ) {    list.add( function() {      // state = [ resolved | rejected ]      state = stateString;    // [ reject_list | resolve_list ].disable; progress_list.lock    }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );  }  // deferred[ resolve | reject | notify ]  deferred[ tuple[ 0 ] ] = function() {    deferred[ tuple[ 0 ] + "With" ]( this === deferred ? promise : this, arguments );    return this;  };  deferred[ tuple[ 0 ] + "With" ] = list.fireWith;} );

通過jQuery.each遍歷tuples數組,并對其進行相關操作,比如我們拿tuples數組中的第一個元素舉例:

['resolve', 'done', jQuery.Callbacks('once memory'), 'resolved']

第一步、聲明的變量list指向jQuery.Callbacks返回的對象,stateString取值為'resolved'

第二步、為promise添加'done'屬性,并指向第一步中list.add(fail和progress即指向屬于各自的自定義事件對象)

第三步、判斷stateString值,如果為'resolved'或'rejected'狀態,那么就添加三個事件函數到對應的list列表中:

  • --改變state狀態的函數
  • --禁止對應狀態的處理,如'resolved'后,那么必定不會觸發rejected狀態咯,反之亦然
  • --禁止pending狀態,都'resolved'或者'rejected'了,那么deferred肯定不會處于pending狀態咯

第四步、為對象deferred,添加觸發各自狀態('resolved','rejected','pending')的fire相關方法:

  • --resolve、resolveWith
  • --reject、rejectWith
  • --notify、notifyWith

好了,jQuery.each(tuples, function(i, tuple){…})解讀就到此結束了。

總結:

通過jQuery.each遍歷tuples,將tuples里的三種狀態操作值done、fail以及progress添加到promise對象,并分別指向各自自定義對象中的add方法。如果狀態為resolved或rejected,那么,再將三個特定函數添加到各自自定義對象的list列表下。隨后,就是對deferred對象賦予三個狀態各自的觸發事件啦。

至此,promise、deferred對象如下圖所示:

 

我們在前面講解promise對象時,提到過它的promise屬性,即為擴展promise對象,再回顧下:

 

所以接下來,源代碼中的promise.promise(deferred),即為擴展deferred對象,讓原來只有6個觸發屬性的deferred,同時擁有了promise對象的全部屬性。

緊接著,func.call(deferred, deferred),即為執行參數func,當然,前提是func有值。值得注意的是,是將deferred作為func的執行對象以及執行參數的,這一點在promise.then中體現得淋淋盡致(稍后會細說)。

最后$.Deferred返回構建好的deferred對象。

到此,構建deferred整體流程走完。

三、細說promise.then

promise.then源碼如下:

promise = {  then: function( /* fnDone, fnFail, fnProgress */ ) {      var fns = arguments;    return jQuery.Deferred( function( newDefer ) {      jQuery.each( tuples, function( i, tuple ) {        var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];        // deferred[ done | fail | progress ] for forwarding actions to newDefer        deferred[ tuple[ 1 ] ]( function() {          var returned = fn && fn.apply( this, arguments );          if ( returned && jQuery.isFunction( returned.promise ) ) {            returned.promise()              .progress( newDefer.notify )              .done( newDefer.resolve )              .fail( newDefer.reject );          } else {            newDefer[ tuple[ 0 ] + "With" ](              this === promise ? newDefer.promise() : this,              fn ? [ returned ] : arguments            );          }        } );      } );      fns = null;    } ).promise();  }}

精簡promise.then的源碼如下:

promise = {  then: function( /* fnDone, fnFail, fnProgress */ ) {      var fns = arguments;    return jQuery.Deferred( function( newDefer ) {          ...        } ).promise();  }}

整體架構上,可以清晰的看出,promise.then方法最后通過jQuery.Deferred返回了一個新的受限制的deferred對象,即deferred.promise,正因為這樣,所以執行完then方法后,我們是不能通過deferred.pomise手動觸發resolve、reject或notify的。

接下來,我們再一步一步剖析promise.then源碼。

var fns = arguments不過就是將then方法中的參數賦予fns,在接下來的jQuery.each里使用。接著,就通過jQuery.Deferred返回了一個構建好的deferred對象,但是注意,在jQuery.Deferred里有個參數―匿名函數,還記得在上一小節末尾處,我們說過如果jQuery.Deferred里有值,就執行它,并將構建好的deferred作為執行對象和參數傳入么: 

固,promise.then方法中的newDefer指向通過jQuery.Deferred構建好的deferred。

緊接著,jQuery.each(tuples, function(i,tuple){…})處理,重點就是deferred[tuple[1]](function(){…});,注意,這里的deferred是then方法的父deferred哦,如下:

且tuple[1]為―done|fail|progress,在前面我們已經談過,它們指向各自自定義事件對象的add方法。因此,也就明白了為什么deferred.resolve|reject|notify后,如果隨后有then,會觸發then方法的相關事件,如下:

但是,then方法后有then方法,又是怎么操作的呢?

它會判斷then方法中的回調函數的返回值,如果是一個deferred對象,那么就將then方法自行創建的deferred對象中的相關觸發事件,添加到回調函數中返回的deferred對象的對應的list列表中,這樣,當我們觸發回調函數中的相關觸發事件后,也就會觸發then方法的deferred對象了,從而,如果then方法后有then方法,也就關聯了。

好了,那么如果then方法中的回調函數的返回值是一個非deferred對象呢?那么它就將這個返回值帶上,直接觸發then方法自行創建的deferred對象的相關事件,從而,如果then方法后有then方法,也就關聯了。

好了,promise.then方法解決就算基本完畢。

四、思考

細細品來,大家有沒有發現,其實promise.then就是通過作用域鏈,利用jQuery.Deferred中的變量deferred來關聯父deferred的。如果,你還記得數據結構中的單鏈表,有沒有發覺似曾相識呢,作者在這里通過jQuery.Deferred這個工廠構建每個deferred,然后利用作用域鏈相互關聯,就如同單鏈表一樣。

因此,借助這一思想,我們就一同模擬一個非常簡單的Deferred,稱作SimpleDef。主要作用就是每次我們執行SimpleDef函數,它都會返回一個構建好的simpleDef對象,該對象里面包含了三個方法done、then以及fire:

  • --done就如同add方法般,將done里的參數添加到它父simpleDef列表list中,并返回父simpleDef對象;
  • --then就是將其參數func添加到父SimpleDef對象的列表list中,并返回一個新SimpleDef對象;
  • --fire就是觸發對應simpleDef對象的list列表里的所有函數。

實現代碼如下:

 function SimpleDef(){  var list = [],    simpleDef = {      done: function(func){        list.push(func);        return simpleDef;      },      then: function(func){        list.push(func);        return SimpleDef();      },      fire: function(){        var i = list.length;        while(i--){          list[i]();        }      }    };  return simpleDef;}

測試代碼如下: 

var def = SimpleDef();var then1 = def.done(function(){  console.log('self1-done1');}).done(function(){  console.log('self1-done2');}).then(function(){  console.log('self2-then1');}).done(function(){  console.log('self2-done1');});def.fire();//=>self2-then1 self1-done2 self1-done1console.log('xxxxxxxxxxxxxxxxxxxx');then1.fire();//=>self2-done1

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 昆明市| 武陟县| 钟祥市| 藁城市| 冷水江市| 泾源县| 绥宁县| 长顺县| 麦盖提县| 彩票| 嘉义县| 临泽县| 论坛| 榆林市| 罗江县| 九江市| 长海县| 连南| 深圳市| 仁布县| 福安市| 女性| 河源市| 富锦市| 尼木县| 西青区| 瑞安市| 茶陵县| 安徽省| 祁阳县| 蓬溪县| 邵东县| 许昌市| 泰兴市| 宁都县| 宜兰市| 邛崃市| 将乐县| 马关县| 望江县| 新田县|