underscore源碼學習(二)

順着underscore源碼的順序讀下來,弄懂了以前underscore的基本結構,接下來看看underscore爲咱們提供的一些關於集合的API。javascript

迭代

關於迭代,咱們都知道ES5原生方法也提供了迭代函數供咱們使用,而在underscore中的迭代則是對原生的迭代函數進行了封裝優化升級。在underscore中,迭代的對象不單單是數組對象,還支持Array,Object的迭代,對Object的迭代的依據是對象的鍵值對(key-value),看看 underscore中_.each是如何實現的:java

/**
 * each方法將ES5的forEach換爲了函數式表達
 * @param obj 待迭代集合
 * @param iteratee 迭代過程當中每一個被迭代元素的回調函數
 * @param context 上下文
 * @example
 * // 數組迭代
 * _.each([1, 2, 3], alert);
 * // 對象迭代
 * _.each({one: 1, two: 2, three: 3}, alert);
 */
_.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
};

看以上源碼可知,_.each傳入三個參數,主要的是第二個iteratee回調函數,而後再經過optimizeCb優化回調,返回對應的回調(optimizeCb能夠查看第一部分)。
array迭代的是數組的每一個元素,傳入的三個參數分別爲數組的值,對應值的下標,數組自己Object迭代的元素是對象的每一個鍵值對key-value,傳入的參數爲對象的key所對應的值,對象的key值,對象自己編程

map-reduce

ES5原生方法也提供map和reduce方法,它們提供了一種對列表操做的思路,是函數式編程重要組成部分。具體map和reduce能夠去MDN上查看相關API。數組

map在underscore中的實現

它的實現思路是:緩存

  • 返回一個新的列表或元素
  • 對列表中的值進行遍歷,用指定函數func做用於每一個遍歷的元素,輸出一個新的值放到新的列表中
_.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
}

使用用例:
對數組使用app

var res = _.map([1,2,3], function(elem, index, array) {
  return elem * 2
})
// => [2,4,6]

對對象使用ide

var obj = {
 name: 'lzb',
 age: '20',
 sex: 'male'
}
var res = _.map(obj, function(value, key, obj) {
  return key
})
// => name age sex

reduce在underscore中的實現

reduce相對於map的實現複雜了一些,underscore首先在外部實現了reduce函數的工廠函數createReduce,這個函數實現瞭如下功能:函數式編程

  • 區分reduce的開始方向(參數dir),是從首端開始末端開始
  • memo記錄最新的結果

reduce的執行過程大概是:函數

  • 設置一個memo變量緩存當前規約過程的結果
  • 若是用戶爲初始化memo,則memo的值爲序列的第一個值
  • 遍歷當前集合,對當前遍歷到的元素按傳入的func進行規約操做,刷新memo
  • 遍歷結束,返回memo

createReduce的實現:學習

/**
 * reduce函數的工廠函數, 用於生成一個reducer, 經過參數決定reduce的方向
 * @param dir 方向 left or right
 * @returns {function}
 */
function createReduce(dir) {
  function iterator(obj, iteratee, memo, keys, index, length) {
    for (; idnex > 0 && index < length; index += dir) {
      var currentKey = keys ? keys[index] : index
      // memo 用來記錄最新的 reduce 結果
      // 執行 reduce 回調, 刷新當前值
      memo = iteratee(memo, obj[currentKey], currentKey, obj)
    }
  }
  /**
   * @param obj 傳入的對象
   * @param iteratee 回調函數
   * @param memo 初始化累加器的值
   * @param context 執行上下文
   */
  return function (obj, iteratee, memo, context) {
    iteratee = optimizeCb(iteratee, context, 4)
    var keys = !isArrayLike(obj) && _.keys(obj),
      length = (keys || obj).length
    index = dir > 0 ? 0 : length - 1
    // 若是沒有傳入memo初始值 則從左第一個爲初始值 從右則最後一個爲初始值
    if (arguments.length < 3) {
      memo = obj[keys ? keys[index] : index]
      index += dir
    }
    return iterator(obj, iteratee, memo, keys, index, length)
  }
}

最後,underscore暴露了兩個供使用的方法

// 由左至右進行規約
_.reduce = _.foldl = _.inject = createReduce(1);
// 由右至左進行規約
_.reduceRight = _.foldr = createReduce(-1);

使用用例:
對數組使用

var sum = _.reduce([1,2,3,4], function(prev, current, index, arr) {
  return prev + current
}, 0)
// => 10

對對象使用

var scores = {
  english: 93,
  math: 88,
  chinese: 100
};
var total = _.reduce(scores, function(prev, value, key, obj){
  return prev+value;
}, 0);
// => total: 281

真值檢測函數

在underscore中,除了提供_.each,_.map._.reduce等函數操做集合,還提供了_.filter, _.reject, _.every, _.some這幾個基於邏輯判斷的集合操做函數。這些API都依賴於用戶提供的真值檢測函數來返回對應的結果。
在underscore中,真值檢測函數的參數被命名爲predicatepredicate有斷言的意思,很是形象。固然,predicate依舊會經過cb優化。

_.filter

看看_.filter的實現

/**
 * 根據真值檢測函數 過濾對象 
 * 檢測經過符合條件 保留元素
 * @param obj
 * @param predicate
 * @param context
 * @example 
 * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
 * => [2, 4, 6]
 */
_.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
}

根據傳入的元素信息,檢測並返回對應boolean值,決定當前元素要被保留。

_.reject

上面的_.filter函數是元素符合檢測條件就保留,而_.reject函數則是與_.filter相反的功能
咱們來看看underscore中_.reject的實現

/**
 * filter的反運算,
 * 若是真值檢測經過, 元素被丟棄
 */
_.reject = function (obj, predicate, context) {
  return _.filter(obj, _negate(cb(predicate)), context)
}

能夠看到,這個函數只有一行代碼,很是簡短。那麼,這其中的_.negate函數又是什麼呢?猜想下,negate在英語中有否認的意思,那麼跟_.reject的功能就有了必定的聯繫, 下面看看_.negate的實現

_.negate = function(predicate) {
  return function() {
    return !predicate.apply(this, arguments)
  }
}

能夠看到,_.negate獲得了反義predicate的執行結果,減小了大量重複的代碼,值得學習。

_.every

迭代對象裏的每一個元素,只有每一個元素都經過真值檢測函數,才返回true

/**
 * @param obj
 * @param predicate
 * @param context
 * @example
 * _.every([true, 1, null, 'yes'], _.identity);
 * => false
 */
_.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
}

_.some

這個API跟_.every差很少,從英語單詞的含義咱們也能夠猜出它的功能,即迭代對象的全部元素,若是有任意一個經過真值檢測,則返回true

/**
 * @param obj
 * @param predicate
 * @param context
 * @example
 * _.some([null, 0, 'yes', false]);
 * => true
 */
_.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
}

_.contains

_.contains函數的功能是檢測一個對象或數組是否包含指定的某個元素。

/**
 * @param obj 待檢測對象
 * @param item 指定的元素
 * @param fromIndex 從哪一個位置開始找
 * @param guard
 * @example
 * _.contains([1,2,3], 3)
 * => true
 */
_.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
}

從代碼上看,仍是比較容易理解的,這裏主要用到了underscore內部提供的兩個函數,_.values_.indexOf,從名字上咱們也能夠猜出它們之間的功能,若是傳入的對象,則取出該對象全部的值,而後再進行查找比較,看看_values的實現:

/**
 * 得到一個對象的全部value
 * @param obj 對象
 * @returns {Array} 值序列
 * @example
 * _.values({one: 1, two: 2, three: 3});
 * // => [1, 2, 3]
 */
_.values = function (obj) {
  var keys = _.keys(obj)
  var length = keys.length
  var values = Array(length)
  for (var i = 0; i < length; i++) {
    values[i] = obj[keys[i]]
  }
  return values
}

_,indexOf的實現就比較複雜了,這是underscore中提供的關於查找的API,詳細介紹將在下一篇總結寫出。

相關文章
相關標籤/搜索