1625行,解開 underscore.js 的面紗 - 第二章

在第二小章節裏面我按照源碼順序介紹幾個方法,源碼緊接着第一章繼續:html

var builtinIteratee;

builtinIteratee,內置的 Iteratee (迭代器)。數組

var cb = function(value, context, argCount) {
    if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
    if (value == null) return _.identity;
    if (_.isFunction(value)) return optimizeCb(value, context, argCount);
    if (_.isObject(value)) return _.matcher(value);
    return _.property(value);
  };

cb 函數接受三個參數,陸續四個判斷,第一個判斷 _.iteratee,根據 JAVASCRIPT 的上下文,首先 builtinIteratee 爲 undefined,然 cb 函數內 builtinIteratee 爲 undefined,接下來就是 _.iteratee = builtinIteratee 裏面的 cb 函數,so...接着第二個判斷傳入參數是否爲空值,若是是則返回 _.identity 函數,即當前傳入值。第三個判斷傳入值是方法則執行 optimizeCb 函數。第四個判斷若是是對象執行返回一個斷言函數,用來斷定傳入對象是否匹配attrs指定鍵/值屬性。都不匹配最後執行 _.property,返回傳入的對象的 key 屬性。app

_.iteratee = builtinIteratee = function(value, context) {
    return cb(value, context, Infinity);
  };

_.iteratee 這個函數通常認爲是一個迭代器,這裏是做者的主觀寫法,由於從意義上講, cb 函數和 _.iteratee 函數很類似,甚至說只要稍加改動 cb 徹底能夠替換掉 _.iteratee,做者用 _.iteratee 包裝 cb 並提供外部訪問,雖然實際工做中咱們運用 _.iteratee 函數並不常見,但若是用的好絕對是一利器,由 underscore.js 源碼內部隨處可見的 cb(),就知道這一函數的做用之大。在 underscorereturn cb() 傳入了第三個參數 Infinity,意爲參數類型爲 Infinity 當執行第三個 cb 函數的 if 判斷,執行 return optimizeCb(); 時就會發揮其做用,Infinity 類型也蠻有意思,有興趣的同窗能夠參考 InfinityPOSITIVE_INFINITYNEGATIVE_INFINITY框架

var restArgs = function(func, startIndex) {
    startIndex = startIndex == null ? func.length - 1 : +startIndex;
    return function() {
      var length = Math.max(arguments.length - startIndex, 0);
      var rest = Array(length);
      for (var index = 0; index < length; index++) {
        rest[index] = arguments[index + startIndex];
      }
      switch (startIndex) {
        case 0: return func.call(this, rest);
        case 1: return func.call(this, arguments[0], rest);
        case 2: return func.call(this, arguments[0], arguments[1], rest);
      }
      var args = Array(startIndex + 1);
      for (index = 0; index < startIndex; index++) {
        args[index] = arguments[index];
      }
      args[startIndex] = rest;
      return func.apply(this, args);
    };
  };

restArgs(其他的參數),什麼意思呢,咱們看它傳入了一個 function 和 一個 Number 類型的 startIndex 標識,首先處理的是 startIndex。三元運算判斷 startIndex 是否存在,是則爲 +startIndex,不然爲 func.length - 1 即傳入 function 中的傳入形參的數量減一,舉個例子如:ide

var aFunction = function(a,b,c){};
  function(a){
      console.log(a.length)    //3
  }

這麼作的目的是什麼呢,咱們都知道在一個 Array 中數組排序是從 0 開始,因此就不難理解 func.length - 1,可是 +startIndex 又是爲何呢,答案是一樣是考慮數組排序是從 0 開始。其實在源碼中 restArgs 這個內部函數做者還並無用到過 startIndex 這個參數,若是須要使用那麼它的意義在於 return function 的時候處理 function 中的一部分參數,咱們如今假設使用了 startIndex 參數,若是 startIndex >2 即拋去 arguments[startIndex + 1] 做爲傳入參數的一步限定,而後將 arguments[arguments.length - startIndex + 1] ~ arguments[arguments.length] 封裝數組做爲 arguments[startIndex] 傳入,固然這過程當中須要將 arguments[arguments.length - startIndex + 1] ~ arguments[arguments.length] 從 arguments 刪除,因此源碼中運用了多個 Array 用於這一過程其目的就是重組 arguments。而當 0<startIndex<2 時,同窗們應該很容易理解 switch (startIndex),這裏就再也不多說了。
前面說到做者並無使用 startIndex 這個參數,那麼沒有 startIndex 是什麼狀況呢,startIndex = func.length - 1 就是說設定 Array 的長度即 arguments 的長度,咱們能夠看到做者對 restArgs 這個函數很重視,而且好像一直在優化它,做者想要作什麼也不得而知,畢竟拋開 startIndex 的話:函數

var restArgs = function(func) {
    startIndex = func.length - 1;
    return function() {
      var rest = Array(1);
      rest[0] = arguments[startIndex];
      var args = Array(arguments.length);
      for (index = 0; index < startIndex; index++) {
        args[index] = arguments[index];
      }
      args[startIndex] = rest;
      return func.apply(this, args);
    };
  };

等同於:優化

var restArgs = function(func) {
    return function() {
      return func.apply(this, arguments);
    };
  };

做者將5行代碼擴展到21行,其實就是爲了一個 startIndex 而已。ui

var baseCreate = function(prototype) {
    if (!_.isObject(prototype)) return {};
    if (nativeCreate) return nativeCreate(prototype);
    Ctor.prototype = prototype;
    var result = new Ctor;
    Ctor.prototype = null;
    return result;
  };

baseCreate 用於建立一個乾淨且只存在具備想要其具備 prototype 的函數,第一個判斷是否具備 prototype 參數,第二個判斷運用 Object.create 建立,餘下則是本身運用 Ctor 這個空函數建立,沒什麼可細說的。this

var property = function(key) {
    return function(obj) {
      return obj == null ? void 0 : obj[key];
    };
  };

property 用於獲取 obj 的 key 值,經過 property() 設置 key ,重點是設置兩個字,有 key 則以沒有則建立之。prototype

var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;

設置 一個最大值 MAX_ARRAY_INDEX,Math.pow(2, 53) - 1 意爲2的53次冪等於9007199254740991,Math 的相關函數參考 Math,其實我一直以爲 MAX_ARRAY_INDEX 並不用設置這麼大的值,Math.pow(2, 16) 就足以。

var getLength = property('length');

設置 obj 的 key 值並生成函數,等同於:

var getLength = function(obj) {
         return obj == null ? void 0 : obj['length'];
    };
var isArrayLike = function(collection) {
    var length = getLength(collection);
    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
  };

isArrayLike,使 Obj 具備 length 屬性且有值則返回 true,不然返回 false,這是一個判斷函數。

_.each = _.forEach = function(obj, iteratee, context) {
    iteratee = optimizeCb(iteratee, context);
    var i, length;
    if (isArrayLike(obj)) {
      for (i = 0, length = obj.length; i < length; i++) {
        iteratee(obj[i], i, obj);
      }
    } else {
      var keys = _.keys(obj);
      for (i = 0, length = keys.length; i < length; i++) {
        iteratee(obj[keys[i]], keys[i], obj);
      }
    }
    return obj;
  };

我一直覺得 JAVASCRIPT 最精華的就是回調的執行方式,雖然互聯網上一些文章總在說回調毀了一切,人云亦云等等,可是回調支撐起了全部的框架,並且回調很優雅用的好能夠很舒服,回調不是毀了一切只是由於某些人不恰當的設置回調毀了他本身的代碼。在 _.forEach 中 iteratee 即回調函數,其中運用了 optimizeCb 優化回調,而後是一個常規判斷,這裏爲何用 isArrayLike(obj) 而不是 isArray(obj) 來判斷是否是數組呢,留下一個思考問題。

_.map = _.collect = function(obj, iteratee, context) {
    iteratee = cb(iteratee, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length,
        results = Array(length);
    for (var index = 0; index < length; index++) {
      var currentKey = keys ? keys[index] : index;
      results[index] = iteratee(obj[currentKey], currentKey, obj);
    }
    return results;
  };

封裝 map 函數,沒什麼好說的,參考 MapMap.prototypeWeakMap 用於知識儲備,至於做者的 _.map 更多的是根據必定的條件遍歷 obj 中的元素,與 _.forEach 的更大區別是 _.forEach 不會對傳入的 obj 作改動直接 return obj,而 _.mapreturn resultsreturn results 是每一個 iteratee 回調的集合。

var createReduce = function(dir) {
    var reducer = function(obj, iteratee, memo, initial) {
      var keys = !isArrayLike(obj) && _.keys(obj),
          length = (keys || obj).length,
          index = dir > 0 ? 0 : length - 1;
      if (!initial) {
        memo = obj[keys ? keys[index] : index];
        index += dir;
      }
      for (; index >= 0 && index < length; index += dir) {
        var currentKey = keys ? keys[index] : index;
        memo = iteratee(memo, obj[currentKey], currentKey, obj);
      }
      return memo;
    };
    return function(obj, iteratee, memo, context) {
      var initial = arguments.length >= 3;
      return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
    };
  };

createReduce,建立 reduce。關於 reduce 的介紹可見 reduce 方法 (Array) (JavaScript):https://msdn.microsoft.com/library/ff679975(v=vs.94).aspxarray-reduce,做者這裏的 reduce 確定不是這樣,但既然命名爲 createReduce,想來也脫不了太多關係。函數中 reducer 首先定義 keys,其值爲 obj 的 key 集合或者 false,後面幾個語句裏都有對於 keys 的三元運算,目的就是排除 obj 不爲 Object 的可能性。接下來判斷傳入 initial,若是傳入 initial 爲 false 則默認 memo 值爲 keys[keys.length-1] || 0,以後是 for 循環遍歷回調,並返回最後一個回調值。跳出 reducer 函數 return function 的偏偏是引用 reducer 函數的外部接口,因而全部一切都連貫上了,包括 initial 的定義是 arguments 長度大於等於3等等。
咱們再從新過一遍代碼,在最外部 return 的時候判斷 initial,實際上就是再肯定是否傳入了 memo 和 context,固然最主要的就是 memo,以此來肯定在內部 reducer 的時候是否具備初始值。在這裏我以爲做者應該對 memo 進行類型判斷的,若是是 Number 或者 String 還說的過去,可是若是傳入 memo 是 Object 就有點說不過去了,會出錯的。好比:

_.reduce([1, 2, 3], function(memo, num){ return memo + num; });
    6
    _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 1);
    7
    _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, '1');
    "1123"
    _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, []);
    "123"
    _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, [1,2]);
    "1,2123"
    _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, {a:1});
    "[object Object]123"
_.reduce = _.foldl = _.inject = createReduce(1);

這裏就是用 createReduce 包裝好的 _.reduce,不解釋。

_.reduceRight = _.foldr = createReduce(-1);

這裏就是用 createReduce 包裝好的 _.reduceRight,與 _.reduce 計算順序相反即從右面向左面開始。

相關文章
相關標籤/搜索