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

每次小章節的開題都煩惱寫什麼好,因此直接接下文 (~o▔▽▔)~o o~(▔▽▔o~) 。數組

_.first = _.head = _.take = function(array, n, guard) {
    if (array == null) return void 0;
    if (n == null || guard) return array[0];
    return _.initial(array, array.length - n);
  };

_.first 用於返回數組中從左到右指定數目 n 的結果集,傳入 array、n、guard 三個參數中 array 只能爲 Array,當 n = null 時返回數組第一個元素,這裏須要講解的是 _.initial 函數是與 _.first 徹底對立的函數,它用於返回數組中從左到右指定數目 Array.length - n 的結果集。app

_.initial = function(array, n, guard) {
    return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
  };

那麼它是如何實現的呢,依然是應用數組 Array 的 Array.prototype.slice.call(array, start, end); 實現,這個概念請參看:Array.prototype.slice()函數

_.last = function(array, n, guard) {
    if (array == null) return void 0;
    if (n == null || guard) return array[array.length - 1];
    return _.rest(array, Math.max(0, array.length - n));
  };

_.last 是返回數組中從右到左指定數目 n 的結果集。實現原理依舊 Array.prototype.slice.call(array, start, end);this

_.rest = _.tail = _.drop = function(array, n, guard) {
    return slice.call(array, n == null || guard ? 1 : n);
  };

_.rest 用於返回數組中從右到左指定數目 Array.length - n 的結果集。prototype

_.compact = function(array) {
    return _.filter(array, Boolean);
  };

_.compact,我喜歡稱它爲過濾器,過濾壞的數據,那麼什麼樣的數據爲壞數據呢,咱們能夠看下 _.filter,前面講 _.filter 接收三個參數 obj, predicate, context,其中 predicate 依舊由 cb 處理,那麼這裏 _.compact 傳的 predicate 是 Boolean = function Boolean() { [native code] },這是一個 JAVASCRIPT 內置的函數用於 Boolean 判斷,咱們能夠參考 BooleanBoolean data type。那麼重點來了,什麼的值會是 Boolean 函數斷言爲 false 呢,答案就是 false, 0, "", null, undefined, NaN,這個可不是我瞎說或者 copy 官網,我是有理論依據的(vˍv),噹噹噹,看這裏 Truthyrest

var flatten = function(input, shallow, strict, output) {
    output = output || [];
    var idx = output.length;
    for (var i = 0, length = getLength(input); i < length; i++) {
      var value = input[i];
      if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
        if (shallow) {
          var j = 0, len = value.length;
          while (j < len) output[idx++] = value[j++];
        } else {
          flatten(value, shallow, strict, output);
          idx = output.length;
        }
      } else if (!strict) {
        output[idx++] = value;
      }
    }
    return output;
  };

flatten 傳入四個參數,input, shallow, strict, output,其中咱們能夠經過 flatten 內部的 for 循環中 length = getLength(input); 知道 input 數據類型爲 Array。而後經過對 shallow, strict 兩個 Boolean 型變量的控制執行相應的數據處理方式。好比 shallow 爲 false 會一直執行 flatten(value, shallow, strict, output);output[idx++] = value; 對多維數組進行一維數組的轉換。code

_.flatten = function(array, shallow) {
    return flatten(array, shallow, false);
  };

_.flatten 函數用於對多維度數組進行扁平化處理,即將任意維數的數組轉換爲一維數組,上面已經說到了這個的實現方式。對象

_.without = restArgs(function(array, otherArrays) {
    return _.difference(array, otherArrays);
  });

_.without 用於刪除數組中的某些特定元素。它由 _.difference 構成。排序

_.uniq = _.unique = function(array, isSorted, iteratee, context) {
    if (!_.isBoolean(isSorted)) {
      context = iteratee;
      iteratee = isSorted;
      isSorted = false;
    }
    if (iteratee != null) iteratee = cb(iteratee, context);
    var result = [];
    var seen = [];
    for (var i = 0, length = getLength(array); i < length; i++) {
      var value = array[i],
          computed = iteratee ? iteratee(value, i, array) : value;
      if (isSorted) {
        if (!i || seen !== computed) result.push(value);
        seen = computed;
      } else if (iteratee) {
        if (!_.contains(seen, computed)) {
          seen.push(computed);
          result.push(value);
        }
      } else if (!_.contains(result, value)) {
        result.push(value);
      }
    }
    return result;
  };

_.uniq 是數組去重,實現原理是若是 isSorted 及後面元素省略,那麼 _.uniq 簡化爲:ip

_.uniq = _.unique = function(array) {
        context = null;
        iteratee = null;
        isSorted = false;
        var result = [];
        var seen = [];
        for (var i = 0, length = getLength(array); i < length; i++) {
          var value = array[i];
          if (!_.contains(result, value)) {
            result.push(value);
          }
        }
        return result;
      };

咱們能夠看到其核心代碼只有 if (!_.contains(result, value)),用於判斷數組中是否包含其值,以此達到數組去重的目的。是這裏我想說的是 context、iteratee、isSorted 變成了未定義的參數,做者沒有處理它會在這種狀況下變成全局污染。
接下來咱們說一下傳入 array, isSorted, iteratee 三個參數的狀況,咱們已經知道 isSorted 默認爲 false,表明去重,那麼若是定義 isSorted 爲 true 則就是不去重,若是 isSorted 是回調函數,則默認內部從新定義 isSorted 爲 false,並將回調函數賦給 iteratee,而後很悲劇的 iteratee 參數依然是沒有 var 過的,又污染了啊(‧_‧?) 。大體就是這醬了。

_.union = restArgs(function(arrays) {
    return _.uniq(flatten(arrays, true, true));
  });

_.union 對多個一維數組進行並運算,實際上就是增強版的 _.uniq。在代碼中做者首先用 flatten 函數處理參數,以前咱們說到 flatten 是用於多個多維數組進行一位轉換,實際上就是要把 arrays 轉換。這裏有同窗可能問道 flatten 直接收一個 Array 剩下的值是 Boolean 啊,那麼使用 _.union 的時候是一次性傳入 n 個 Array(如這樣:_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);),說不通啊。因此我要說的是 restArgs 這個函數,將傳入參數轉換爲一個數組進行 func.apply(this, args) 到 restArgs 的回調函數 function(arrays) {} 中,以此達到 flatten 函數 arrays 接到的是一個一維數組的集合。最後經過 _.uniq 函數對數組進行處理。

_.intersection = function(array) {
    var result = [];
    var argsLength = arguments.length;
    for (var i = 0, length = getLength(array); i < length; i++) {
      var item = array[i];
      if (_.contains(result, item)) continue;
      var j;
      for (j = 1; j < argsLength; j++) {
        if (!_.contains(arguments[j], item)) break;
      }
      if (j === argsLength) result.push(item);
    }
    return result;
  };

_.intersection 用於獲取多個一維數組的相同數據的集合,即交集。又是一番對 Array 的 for 啊 for 啊 for,而後 if 而後 push,相信你們這麼聰明,不用多說了,由於這個函數很直白,沒太多可講的。

_.difference = restArgs(function(array, rest) {
    rest = flatten(rest, true, true);
    return _.filter(array, function(value){
      return !_.contains(rest, value);
    });
  });

_.difference 函數的實現與 _.union 相似,都是經過 restArgs 對 n 個傳參進行數組轉變,而後賦給回調函數,區別在於這個函數可能更加複雜,它首先 restArgs 回調寫了兩個傳參 array, rest,但實際上 rest 是 undefined,以後在回調內部給 rest 賦值爲 flatten 函數處理以後的數組,即扁平化後的一維數組。由於 restArgs 函數只有一個 function 回調,因此內部執行 return func.call(this, arguments[0], rest);,返回的是第一個數組和其餘數組的集合,即 array, rest

_.unzip = function(array) {
    var length = array && _.max(array, getLength).length || 0;
    var result = Array(length);
    for (var index = 0; index < length; index++) {
      result[index] = _.pluck(array, index);
    }
    return result;
  };

_.unzip 用於將多個數組中元素按照數組下標進行拼接,只接收一個二維數組,返回值一樣是一個二維數組。

_.zip = restArgs(_.unzip);

_.zip_.unzip 不一樣之處在於它能夠傳入不定的一維數組參數而後經過 restArgs 函數轉換實現 _.unzip 傳參的效果。

_.object = function(list, values) {
    var result = {};
    for (var i = 0, length = getLength(list); i < length; i++) {
      if (values) {
        result[list[i]] = values[i];
      } else {
        result[list[i][0]] = list[i][1];
      }
    }
    return result;
  };

_.object 用於將數組轉換成對象。

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;
    };
  };

createPredicateIndexFinder 這個函數適用於生成 _.findIndex 之類的函數,當咱們看到 return index; 的是後就已經能夠知道,其核心是與數組下標有關。

_.findIndex = createPredicateIndexFinder(1);

_.findIndex 函數由 createPredicateIndexFinder 包裝而成,咱們能夠看到它的默認傳值是 1,也就是:

_.findIndex = function(array, predicate, context) {
       predicate = cb(predicate, context);
       for (var index >= 0; index < getLength(array); index += 1) {
           if (predicate(array[index], index, array)) return index;
       }
       return -1;
   };

其中 predicate 是回調函數接收 array[index], index, array 三個值用於 Boolean 判斷,最終結果是返回符合規則的數組中的第一條數據的數組下標。

_.findLastIndex = createPredicateIndexFinder(-1);

_.findLastIndex 顧名思義就是返回數組中符合規則的最後一條數據的下標,說直白了就是遍歷數組的時候從右往左而已。

_.sortedIndex = function(array, obj, iteratee, context) {
    iteratee = cb(iteratee, context, 1);
    var value = iteratee(obj);
    var low = 0, high = getLength(array);
    while (low < high) {
      var mid = Math.floor((low + high) / 2);
      if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
    }
    return low;
  };

_.sortedIndex 官網解釋說 使用二分查找肯定value在list中的位置序號,value按此序號插入能保持list原有的排序。,很繞口,這裏咱們須要注意的是若是進行 _.sortedIndex 查找這個特定的序列號,必定要事先將 array 進行按需排序。

var createIndexFinder = function(dir, predicateFind, sortedIndex) {
    return function(array, item, idx) {
      var i = 0, length = getLength(array);
      if (typeof idx == 'number') {
        if (dir > 0) {
          i = idx >= 0 ? idx : Math.max(idx + length, i);
        } else {
          length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
        }
      } else if (sortedIndex && idx && length) {
        idx = sortedIndex(array, item);
        return array[idx] === item ? idx : -1;
      }
      if (item !== item) {
        idx = predicateFind(slice.call(array, i, length), _.isNaN);
        return idx >= 0 ? idx + i : -1;
      }
      for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
        if (array[idx] === item) return idx;
      }
      return -1;
    };
  };

createIndexFinder,看命名就能夠知道依舊與數組下標有關。咱們能夠看到數據處理的一個關鍵是 idx,它多是一個數字也多是一個字符串或者對象。當它是 Number 的時候遵循 idx 是限制查找範圍的數組下標規則,若是它是其餘的則使用 sortedIndex 函數查找到 idx 的數組下標再歲數組查找範圍進行限定。

_.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);

_.indexOf 函數與 _.findIndex 區別在於 _.findIndex 須要查找的數據可能存在於數組中也可能不存在數組中,而 _.indexOf 的 predicateFind 必定是數組中的元素。同時也用 array, item, idx 三個參數中的 idx 限定開始查找的範圍。

_.lastIndexOf = createIndexFinder(-1, _.findLastIndex);

_.lastIndexOf 查找數組中的符合結果的最後條數據的數組下標。

_.range = function(start, stop, step) {
    if (stop == null) {
      stop = start || 0;
      start = 0;
    }
    if (!step) {
      step = stop < start ? -1 : 1;
    }
    var length = Math.max(Math.ceil((stop - start) / step), 0);
    var range = Array(length);
    for (var idx = 0; idx < length; idx++, start += step) {
      range[idx] = start;
    }
    return range;
  };

_.range 用於生成一個有序的數組,經過 start 和 stop 限定數組範圍,經過 step 限定差值。

_.chunk = function(array, count) {
    if (count == null || count < 1) return [];
    var result = [];
    var i = 0, length = array.length;
    while (i < length) {
      result.push(slice.call(array, i, i += count));
    }
    return result;
  };

_.chunk,這個函數目前官網並無釋義,估計做者忘記加進去了吧,咱們看到 chunk 很天然的就應該想到 stream 的概念,這裏也差很少,只不過拆分的不限定是 Buffer 數組, _.chunk 傳入兩個參數 Array 以及 count,其中 count 用來限定拆分出的每一組的大小,舉個栗子:

_.chunk([1,2,3,4,5,6,7,8,9], 1)
   [[1],[2],[3],[4],[5],[6],[7],[8],[9]]
   _.chunk([1,2,3,4,5,6,7,8,9], 2)
   [[1,2],[3,4],[5,6],[7,8],[9]]

然而但凡對 stream 的概念有所瞭解都知道這個函數吧,沒什麼特殊的地方。

相關文章
相關標籤/搜索