lodash-12 countBy,every,filter

596 阅读3分钟

前言

今天是周一,lodash 过了一个新的分组,达到了collection 组,今天带来的是 countBy,everyfilter

Api

countBy

Creates an object composed of keys generated from the results of running each element of collection thru iteratee. The corresponding value of each key is the number of times the key was returned by iteratee. The iteratee is invoked with one argument:  (value) .

Arguments

  1. collection  (Array|Object) : The collection to iterate over.
  2. [iteratee=_.identity]  (Function) : The iteratee to transform keys.

Returns

(Object) : Returns the composed aggregate object.

Example

_.countBy([6.14.26.3], Math.floor);
// => { '4': 1, '6': 2 }
 
// The `_.property` iteratee shorthand.
_.countBy(['one''two''three'], 'length');
// => { '3': 2, '5': 1 }

countBy 的参数中不难看出,第一个参数是一个数组,第二个是一个 函数或者字符串,可以对前面的数组执行操作汇总

如果是 es6 的话,可以直接使用下面的函数代替

function countBy(arr,iteratee){
 // 对 iteratee 进行进一步的处理,都变成函数
  return arr.reduce((prev,cur)=>{
    const v = iteratee( cur )
    prev[v] = (prev[v] || 0)+1;
    return prev
  },{})
}

看看 lodash 是怎么处理的吧,以 countBy([6.1, 4.2, 6.3], Math.floor) 为例

var countBy = createAggregator(function(result, value, key){
 // 在这里修改了 accumulator
  result[key] = result[key] + 1 || 1
})


// 创建一个聚合器
const  createAggregator = (setter)=>{
  return function(collection, iteratee){
    var func = arrayAggregator,accumulator = {}; // 初始值
     // 引用类型  可以选择直接 return accumulator
    return func(collection, setter, baseIteratee(iteratee), accumulator);
  }
} 

// 数组聚合器
const arrayAggregator = (array, setter, iteratee, accumulator)=>{
  var index = -1,
      length = array == null ? 0 : array.length;

  while (++index < length) {
    var value = array[index];
    // 由于是引用的上文的 accumulator,是一个引用类型,
    // 所以修改 accumulator 是可以改变原对象的
    setter(accumulator, value, iteratee(value), array);
  }
  return accumulator;
}


// 格式化迭代器
var baseIteratee = value =>{
   if (typeof value == "function") {
    return value;
  }else if(typeof value == "string"){
      return (obj)=>{
          return obj[value]
      }
  }
}

通过 createAggregator 目的是创建一个 聚合器,接受一个函数作为参数

createAggregator内部,返回了一个函数,其中的参数 collection 就是用户传递的第一个参数 [6.1, 4.2, 6.3], iterateeMath.floor 函数

createAggregator 内部使用了 arrayAggregator 对数组进行聚合,对每一个数组元素执行 iteratee的结果 作为 累加器 accumulator 的 key 值

由于 累加器 accumulator 是一个引用对象,所以返回
return func(collection, setter, baseIteratee(iteratee), accumulator);
也可以返回accumulator,效果是一样的

从这个函数中,看出了偏函数的用法,函数之间不断的传递参数,对函数的职责做了细分

_.every(collection, [predicate=_.identity])

Checks if predicate returns truthy for all elements of collection. Iteration is stopped once predicate returns falsey. The predicate is invoked with three arguments:  (value, index|key, collection) .

Note:  This method returns true for empty collections because everything is true of elements of empty collections.

Arguments

  1. collection  (Array|Object) : The collection to iterate over.
  2. [predicate=_.identity]  (Function) : The function invoked per iteration.

Returns

(boolean) : Returns true if all elements pass the predicate check, else false.

Example

_.every([true1null'yes'], Boolean);
// => false
 
var users = [
  { 'user''barney''age'36'active'false },
  { 'user''fred',   'age'40'active'false }
];
 
// The `_.matches` iteratee shorthand.
_.every(users, { 'user''barney''active'false });
// => false
 
// The `_.matchesProperty` iteratee shorthand.
_.every(users, ['active'false]);
// => true
 
// The `_.property` iteratee shorthand.
_.every(users, 'active');
// => false

和数组的 every 方法类型,只是它接受的类型更多而已

function every(collection, predicate) {
  return arrayEvery(collection, baseIteratee(predicate));
}

重点放在了 arrayEvery 上,其实原理也很简单

function arrayEvery(array, predicate) {
  var index = -1,
      length = array == null ? 0 : array.length;

  while (++index < length) {
    if (!predicate(array[index], index, array)) {
      return false;
    }
  }
  return true;
}

从源码中不难看出,当 array 是一个空数组的时候,是会返回一个 true,也就是 note 中说的 everything is true

.filter(collection, [predicate=_.identity])

Iterates over elements of collection, returning an array of all elements predicate returns truthy for. The predicate is invoked with three arguments:  (value, index|key, collection) .

Note:  Unlike _.remove, this method returns a new array.

Arguments

  1. collection  (Array|Object) : The collection to iterate over.
  2. [predicate=_.identity]  (Function) : The function invoked per iteration.

Returns

(Array) : Returns the new filtered array.

Example

var users = [
  { 'user''barney''age'36'active'true },
  { 'user''fred',   'age'40'active'false }
];
 

_.filter(users, function(o) { return !o.active; });
// => objects for ['fred']
 
// The `_.matches` iteratee shorthand.
_.filter(users, { 'age'36'active'true });
// => objects for ['barney']
 
// The `_.matchesProperty` iteratee shorthand.
_.filter(users, ['active'false]);
// => objects for ['fred']
 
// The `_.property` iteratee shorthand.
_.filter(users, 'active');
// => objects for ['barney']

source

function filter(collection, predicate) {
  return arrayFilter(collection, baseIteratee(predicate));
}
function arrayFilter(array, predicate) {
  var index = -1,
      length = array == null ? 0 : array.length,
      resIndex = 0,
      result = [];

  while (++index < length) {
    var value = array[index];
    // 断言 value 值为 true
    if (predicate(value, index, array)) {
      result[resIndex++] = value;
    }
  }
  return result;
}

针对这么复杂的情况,需要对 baseiteratee做一些小改动,同时也是对

const baseIteratee = iterate => {
  if (typeof iterate == "function") {
    return iterate;
  }
  
  if (typeof iterate == "string") {
    return obj => obj[iterate];
  }

  if (Array.isArray(iterate)) {
    return obj => {
      return obj[iterate[0]] == iterate[1];
    };
  }

  let metaData = Object.entries(iterate);
  return (object)=>{
    let index = metaData.length;
    let i = -1;
    while (++i < index) {
      let data = metaData[i];
      let objValue = object[data[0]]; 
      let srcValue = data[1]; 
      if (srcValue !== objValue) {
        return false;
      }
    }
    return true
  }
};

结尾

随着不断的学习这个 js库,理解了不少函数编程和参数拆分的技巧,在工作中会有很大的帮助