4.實現jQuery的$.Deferred延遲對象

$.Deferred功能描述

 以下代碼運行三秒後會在控制檯輸出"執行完畢".

$.Deferred函數運行會生成一個延時對象dtd,該對象本身具有很多屬性比如resolve,reject,progress.其中在一個異步函數中,比如下面代碼中的setTimeout或者ajax回調函數裏,如果執行dtd.resolve(),則會立刻執行done裏面的函數回調.dtd.reject()會觸發fail裏面的函數回調.dtd.progress()會觸發notify裏面的函數回調.如果想詳細瞭解$.Deferred延時對象的其他用法可參照jquery文檔.

 var wait = function(){
        var dtd = $.Deferred(); // 生成Deferred對象
    var task = function(){
      dtd.resolve('執行完畢');
    };
    setTimeout(task,3000);
       return dtd; 
  };

 $.when(wait())
  .done(function(msg){ console.log(msg); })
  .fail(function(){ alert("出錯啦!"); });

 

需求分析

下面我們就來實現一下$.Deferred的核心代碼.從上面的調用方式看$.Deferred會返回一個延時對象dtd,dtd上面有resolve,reject和progress方法,由此我們可以確定$.Deferred()的返回值是一個帶有上面三個方法的對象.緊接着我們可以使用上節課所學到的$.Callback函數來實現此需求.對於resolve,reject和progress這三種函數對應的狀態分別創立三個隊列,而後面的done,fail的功能就是往這個隊列中執行add()(收集函數),等到異步的邏輯調用完成時例如調用dtd.resolve(),那麼就對resolve這個狀態的隊列執行fire操作依次運行此隊列的函數,如此變完成了延遲加載函數的需求.

 

代碼實現

將前面講述的jquery源碼系列的第一課的extend函數和第三課的$.Callbacks函數代碼實現拷貝到下面文件中,新建一個Deferred函數並暴露在$對象下,Deferred函數的代碼便是延遲功能的核心部分.

按照前面所提到的在Deferred函數中新建一個dfr空對象,並將它作爲函數的返回值返回.另外需要新建一個promise對象,它裏面有state和promise兩個方法,其中state()方法調用是爲了獲取當前的狀態,也就是對應着函數裏形成的閉包變量let state,另外通過遍歷arr數組給promise加上三種隊列的add方法.而在dfr對象上掛載隊列的fire方法,最後將promise上的所有屬性和方法掛載到dfr上.

$.when(dfr)返回值就是Deferred函數定義的變量promise,所以它擁有done和fail這些方法來完成函數收集並裝載到對應的隊列中,dfr和promise這兩個對象共同操作在函數內部形成閉包的三個隊列,所以dfr一旦執行resolve,reject和progress函數就會將隊列上收集到的函數依次執行.

代碼如下:

(function (root) {
  /**
   * 生成配置
   */
  function genConfig(params) {
    const array = params.split(/\s+/);
    const object = {};
    array.forEach((column) => {
      object[column] = true;
    });
    return object;
  }

  function callback(params) {
    const options = typeof params === 'string' ? genConfig(params) : {};

    const list = [];

    let i, fired, start_index; //fired用來記錄是否已經使用fire函數觸發過

    let memory;

    function fireHandler(context, parameter) {
      fired = true;
      memory = options['memory'] && parameter;
      i = start_index ? start_index : 0;
      for (; i < list.length; i++) {
        if (
          list[i].apply(context, parameter) === false &&
          options['topOnFalse']
        ) {
          break;
        }
      }
      start_index = null;
    }

    const result = {
      add: function () {
        const fn_list = Array.prototype.slice.call(arguments);
        fn_list.forEach((fn) => {
          if (toString.call(fn) === '[object Function]') {
            if (!list.includes(fn) || !options['unique']) {
              list.push(fn);
              if (options['memory'] && fired) {
                start_index = list.length - 1;
                fireHandler(result, memory);
              }
            }
          }
        });
      },
      fire: function () {
        if (!options['once'] || !fired) {
          const parameter = Array.prototype.slice.call(arguments);
          fireHandler(result, parameter);
        }
      },
    };

    return result;
  }

  /**
   * 實現延遲對象功能
   */
  function Deferred() {
    const arr = [
      ['resolve', 'done', callback('once memory'), 'resolved'],
      ['reject', 'fail', callback('once memory'), 'rejected'],
      ['progress', 'notify', callback('memory')],
    ];

    let state = "pending";

    const dfr = {};

    const promise = {
      promise(dfr = null) {
        return dfr === null ? promise : extend(dfr, promise);
      },
      state(){
        return state;
      }
    };

    arr.forEach((item) => {

      const stateString = item[3];
      const list = item[2];

      if(stateString){
        list.add(function (){
          state = stateString;
        })
      }

      dfr[item[0]] = function () {
        if (state !== 'pending') {
          return false;
        }
        list.fire.apply(this, Array.prototype.slice.call(arguments));
      };

      promise[item[1]] = function () {
        const array = Array.prototype.slice.call(arguments);
        array.length > 0 ? list.add(array[0]) : null;
        return promise;
      };
    });

    promise.promise(dfr);

    return dfr;
  }

  const extend = function () {
    var target = arguments[0] || {};

    var i = 1,
      isDeep = false,
      isArrayData = false;

    if (typeof arguments[0] === 'boolean') {
      //第一個參數是boolean型
      isDeep = arguments[0] ? true : false;
      target = arguments[1] || {};
      i = 2;
    }

    var arr = Array.prototype.slice.call(arguments, i);

    if (arguments.length == 1) {
      target = this;
      arr = [arguments[0]];
    }

    arr.forEach((obj) => {
      for (let key in obj) {
        if (isDeep) {
          //深拷貝
          let src = obj[key];
          let des = target[key];
          let copy;
          if (
            jQuery.isPlainObject(src) ||
            (isArrayData = jQuery.isArray(src))
          ) {
            //src是數組或者對象類型
            if (isArrayData) {
              copy = jQuery.isArray(des) ? des : [];
            } else {
              copy = jQuery.isPlainObject(des) ? des : {};
            }

            target[key] = jQuery.fn.extend(isDeep, copy, src); //這一句代碼是精髓之處
          } else {
            target[key] = obj[key];
          }

          isArrayData = false;
        } else {
          target[key] = obj[key];
        }
      }
    });

    return target;
  };

  const $ = {
    Callbacks: callback,
    Deferred: Deferred,
    when(defferd) {
      return defferd.promise();
    },
  };
  root.$ = $;
})(window);

 

結果驗證

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <script src="./lib/deferred.js"></script>
  </head>
  <body>
 
  </body>
  <script>
    var wait = function () {
      var dtd = $.Deferred(); // 生成Deferred對象
      var tasks = function () {
        dtd.resolve('執行完畢');
        dtd.resolve('執行完畢');
        dtd.reject();
      };

      for (i = 0; i < 10; i++) {
        (() => {
          var j = i;
          setTimeout(() => {
            dtd.progress(j * 10);
          }, i * 1000);
        })();
      }
      setTimeout(tasks, 10000);
      return dtd;
    };

      $.when(wait()).done(function (msg) {
        console.log(msg);
      })
      .fail(function () {
        console.log('發生了錯誤');
      })
      .notify(function (data) {
        console.log(data + '%');
      });
  </script>
</html>

輸出結果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章