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

今天繼續上次的內容,以前咱們講到了 reduce 的用法,其實我以爲用法卻是其次的關鍵是做者實現 reduce 過程當中所靈活用到的函數處理方法,咱們只要有心稍加總覺徹底能夠拿來主義,豐富本身的代碼└(^o^)┘。數組

_.find = _.detect = function(obj, predicate, context) {
    var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey;
    var key = keyFinder(obj, predicate, context);
    if (key !== void 0 && key !== -1) return obj[key];
  };

_.find,討論這個函數首先要弄懂 _.findIndex_.findKey,這裏咱們先簡單知道一個是針對數組一個是針對對象,具體的後面讀到源碼再說。傳入值 obj 進行 isArrayLike 判斷以此決定 keyFinder 函數,將三個參數包括回調傳入 keyFinder 中其中 predicate 回調函數充當迭代器進行真值檢測,最後 return obj[key]。閉包

var createPredicateIndexFinder = function(dir) {
        return function(array, predicate, context) {
          predicate = cb(predicate, context);
          var length = getLength(array);
          var index = dir > 0 ? 0 : length - 1;
          for (; index >= 0 && index < length; index += dir) {
            if (predicate(array[index], index, array)) return index;
          }
          return -1;
        };
      };

_.findIndex 爲例簡單介紹一下,_.findIndex 是由 createPredicateIndexFinder 包裝而成,意義在於返回 predicate 函數內部 return true。app

_.filter = _.select = function(obj, predicate, context) {
    var results = [];
    predicate = cb(predicate, context);
    _.each(obj, function(value, index, list) {
      if (predicate(value, index, list)) results.push(value);
    });
    return results;
  };

_.filter 函數與 _.find 相似,內部實現較之 _.find 更簡單些,_.find 意爲匹配 predicate 回調 return true 惟一就近值,_.filter 則是匹配全部值的集合。那麼有人說爲何不用 _.filter()[0] 取代 _.find,理論上兩者確實是相同值,可是 _.filter 會遍歷傳參 obj 直至結束,而 _.find 則是遍歷過程當中匹配成功結束遍歷,因此某些狀況下 _.find 優於 _.filterdom

_.reject = function(obj, predicate, context) {
    return _.filter(obj, _.negate(cb(predicate)), context);
  };

_.reject,經過 _.negatecb 函數包裝 predicate 回調,實際上就是用 optimizeCb 優化 predicate function,而後用 _.negate 返回與 predicate 相反的 Boolean 類型值,以此得到與 _.filter 做用相反的結果集合。函數

_.every = _.all = function(obj, predicate, context) {
    predicate = cb(predicate, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length;
    for (var index = 0; index < length; index++) {
      var currentKey = keys ? keys[index] : index;
      if (!predicate(obj[currentKey], currentKey, obj)) return false;
    }
    return true;
  };

_.every,咱們看源碼中的返回值類型爲 Boolean 知道這是一個用於真值檢測的函數,內部的處理步驟已經很程序化了,首先優化回調函數 predicate,處理傳參 obj(根據 Object 或者 Array),回調中接收 obj[currentKey], currentKey, obj 三個參數進行 Boolean 判斷,當判斷失敗的時候則 if (!false) return false; 結束 for 循環。這個方法看上去很雞肋,但實際上結合 predicate 回調應用於某些判斷處理很給力。優化

_.some = _.any = function(obj, predicate, context) {
    predicate = cb(predicate, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length;
    for (var index = 0; index < length; index++) {
      var currentKey = keys ? keys[index] : index;
      if (predicate(obj[currentKey], currentKey, obj)) return true;
    }
    return false;
  };

_.some,看源碼咱們能夠知道它基本上與 _.every 相似,區別在於 _.some 遍歷 obj 過程當中只要任何一個元素經過 predicate 回調的真值檢測就直接當即中斷遍歷並返回 true。我主觀意識上更偏向於 _.every_.some 用一個相同的基礎函數包裝再經過判斷值構建它們,就像 createReduce 函數構成 _.reduce_.reduceRight 同樣,可是不知道做者爲何沒有這樣作,可能有其餘的考慮吧,這裏再也不揣測。this

_.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
    if (!isArrayLike(obj)) obj = _.values(obj);
    if (typeof fromIndex != 'number' || guard) fromIndex = 0;
    return _.indexOf(obj, item, fromIndex) >= 0;
  };

_.contains 用於檢查 obj 中是否包含 item 值,我更傾向於這是一個簡化版的 _.some,若是是我寫基礎函數可能真的就只有 _.some 不用 _.contains,可是 Undescore.js 做爲一個知名函數庫,在代碼優化的執行速度上確定要比咱們作的更細。
這裏順便說一下 _.indexOfguard_.indexOf 是由 createIndexFinder 包裝而來,能夠理解爲數組版的 indexOf,indexOf 概念可參考 String.prototype.indexOf()Array.prototype.indexOf()。關於 array.indexOf(searchElement[, fromIndex = 0]),我這裏再說幾句,這個 JAVASCRIPT 函數傳入1或2個參數,第一個參數爲將要進行匹配的內容,可爲 Number 可爲 String,第二個可選參數爲(須要定向匹配數組中某一值的數組下標值 - array.length)*n,且 n!= 0array.indexOf 根據這個下標進行定向匹配驗證,若是匹配成功則返回值爲被匹配值的數組下標,匹配失敗則返回 -1。prototype

var array = [2, 9, 9,9,9,3,4];
    undefined
    array.indexOf(9,2);
    2
    array.indexOf(9,3);
    3
    array.indexOf(9,4);
    4
    array.indexOf(9,5);
    -1
    array.indexOf(3,5);
    5
    array.indexOf(5);
    -1
    array.indexOf(2, -7);
    0

_.indexOf 雖然與 array.indexOf(searchElement[, fromIndex = 0]) 有所區別,但也有不少相通之處。rest

_.invoke = restArgs(function(obj, method, args) {
    var isFunc = _.isFunction(method);
    return _.map(obj, function(value) {
      var func = isFunc ? method : value[method];
      return func == null ? func : func.apply(value, args);
    });
  });

_.invoke 用於批量執行方法,前面咱們講了 restArgs 方法,雖然代碼很複雜,但目前實際上只應用了以下簡化的結構:code

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

也就是說 _.invoke 拋開閉包的概念以後等同於:

function(obj, method, args) {
        var isFunc = _.isFunction(method);
        return _.map(obj, function(value) {
          var func = isFunc ? method : value[method];
          return func == null ? func : func.apply(value, args);
        });
      }

其中 _.isFunction 是判斷是否爲 function,接下來 _.map 回調,實際上我很納悶萬一傳入的 method 是 obj[i] 對象上沒有的方法怎麼辦,按照 return 的結果若是沒有則返回 func 也就是 null,總以爲這樣返回缺乏點什麼。

_.pluck = function(obj, key) {
    return _.map(obj, _.property(key));
  };

_.pluck 返回傳入 obj 的 key 的集合,或者說 key 的集合有點武斷,更具體點說是 obj 下第二層所包含 key 的值的集合,而第一層也就是 obj 可爲 Object 或 Array,但 obj 中第二層必須是 Object。這是爲何呢?

_.map(obj, function(key) {
        return (function(obj) {
          return obj == null ? void 0 : obj[key];
        })(key);
      })

在上述簡化的代碼中咱們能夠看出 return obj == null ? void 0 : obj[key]; 的值是 obj[key],因此第二層只能是 Object。

_.where = function(obj, attrs) {
    return _.filter(obj, _.matcher(attrs));
  };

_.where 頗有趣,代碼簡化以後是:

_.where = function(obj, attrs) {
    return _.filter(obj, (function(attrs) {
        attrs = _.extendOwn({}, attrs);
        return function(obj) {
          return _.isMatch(obj, attrs);
        })(attrs);
      });
  };

_.filter 咱們講過是獲取全部匹配值的集合,而回調中的 _.extendOwn 將 attrs 放入空對象 {} 中並 return,_.isMatch是個斷言用於判斷 obj 中是否存在 key-value。那麼 _.where 就是 _.isMatch_.filter 的增強版,它用於判斷一個大的對象數組中存在與傳入 attrs 相同的鍵值對,若是存在則返回匹配目標鍵值對所在的 Object,而且返回值是一個集合。

var list = [{author:"Shakespeare",title:"china"},
        {author:"Shakespeare",year:1611,title:"china"},
        {author:"Shakespeare",year:1611,title:"English"},
        {year:1611,title:"china"}];
    _.where(list, {author: "Shakespeare", year: 1611});
    [{"author":"Shakespeare","year":1611,"title":"china"},{"author":"Shakespeare","year":1611,"title":"English"}]

這個方法在處理數據的時候特別有用。

_.findWhere = function(obj, attrs) {
    return _.find(obj, _.matcher(attrs));
  };

_.findWhere,至關於 _.where()[0],即返回結果集合的第一個值,這麼設定的目的和 _.find_.filter 同樣,運算更快,遍歷到目標立刻中止遍歷。

_.max = function(obj, iteratee, context) {
    var result = -Infinity, lastComputed = -Infinity,
        value, computed;
    if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) {
      obj = isArrayLike(obj) ? obj : _.values(obj);
      for (var i = 0, length = obj.length; i < length; i++) {
        value = obj[i];
        if (value != null && value > result) {
          result = value;
        }
      }
    } else {
      iteratee = cb(iteratee, context);
      _.each(obj, function(v, index, list) {
        computed = iteratee(v, index, list);
        if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
          result = v;
          lastComputed = computed;
        }
      });
    }
    return result;
  };

_.max 用來查找 obj 對象數組中某一 key 的最大值的 Object,限定是 key-value 的 value 必須是 Number 類型。-Infinity 我更喜歡叫它負無窮,這裏的 if true 第一個判斷能夠忽略了,爲何不講了呢,由於做者要放棄 typeof iteratee == 'number' && typeof obj[0] != 'object' 這種狀況,可見其餘版本的 Underscore.js。若是忽略 typeof iteratee == 'number' && typeof obj[0] != 'object' 的狀況則 _.max 傳參爲一個數組,return 爲數組中最大值。if false 則進行常規的 _.each 代碼很簡單這裏再也不講解。

_.min = function(obj, iteratee, context) {
    var result = Infinity, lastComputed = Infinity,
        value, computed;
    if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) {
      obj = isArrayLike(obj) ? obj : _.values(obj);
      for (var i = 0, length = obj.length; i < length; i++) {
        value = obj[i];
        if (value != null && value < result) {
          result = value;
        }
      }
    } else {
      iteratee = cb(iteratee, context);
      _.each(obj, function(v, index, list) {
        computed = iteratee(v, index, list);
        if (computed < lastComputed || computed === Infinity && result === Infinity) {
          result = v;
          lastComputed = computed;
        }
      });
    }
    return result;
  };

_.min 真心不用講了,參考 _.max

_.shuffle = function(obj) {
    return _.sample(obj, Infinity);
  };

_.shuffle 官網釋義是返回一個隨機亂序的 list 副本, 使用 Fisher-Yates shuffle 來進行隨機亂序.Fisher-Yates shuffle 是什麼鬼,咱們這裏看到 _.shuffle 這個函數用到了 _.sample,因此咱們先講 _.sample

_.sample = function(obj, n, guard) {
    if (n == null || guard) {
      if (!isArrayLike(obj)) obj = _.values(obj);
      return obj[_.random(obj.length - 1)];
    }
    var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj);
    var length = getLength(sample);
    n = Math.max(Math.min(n, length), 0);
    var last = length - 1;
    for (var index = 0; index < n; index++) {
      var rand = _.random(index, last);
      var temp = sample[index];
      sample[index] = sample[rand];
      sample[rand] = temp;
    }
    return sample.slice(0, n);
  };

_.sample 是從一個 obj 中隨機返回值,而且返回值受限於 n 這個參數,若是沒有傳入 n 或者傳入了 guard = true 則執行 if 語句,目的是將 obj 判斷處理以後返回單一值。這裏以爲特雞肋有木有,也就是說 _.sample(obj,n,true)_.sample(obj) 是一回事。若是按照 _.sample(obj,n) 的邏輯執行,依賴是老套路,處理 obj (Object 和 Array),而後 n = Math.max(Math.min(n, length), 0); 得到合理的 n 值,前面咱們講到了 Infinity 正無窮和 -Infinity 負無窮,這段代碼利用了 Infinity 的特性包裝了 _.shuffle函數,關鍵就是 Infinity 大於全部 Number 數字,即 Math.min(Infinity, Number) 等於 Number,好處就是讓人眼前一亮,哇,原來代碼還能夠這樣寫,壞處就是當單獨使用 _.sample 函數的 n 大於處理以後的 obj 的長度時並不會報錯,而是默認執行 n=sample.length,仁者見仁,智者見智吧。後面就是很套路的根據數組下標替換數組內容,固然數組下標是經過 _.random 隨機的,而後 slice 一刀切數組。

相關文章
相關標籤/搜索