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,详细介绍将在下一篇总结写出。

相关文章
相关标签/搜索