前言
countBy
函数会创建一个对象,用于有条件的统计第一个参数集合(collection
,包含数组、类数组、对象等可遍历的集合)内的出现的某些种类型的值,对象的 key
值是第二个参数 iteratee
处理后的值的类型,对应的 value
值是第一个参数每个值经过第二个参数 iteratee
处理后返回的 key
的次数
思路分析
源码分析
1. countBy
1. 传入参数
collection
传入一个集合,类型可以是数组、类数组、普通对象等
iteratee
迭代器,这里可以理解为 countBy
聚合进行处理的条件
2. 源码分析
调用 createAggregator
,传入计数函数,result
判断不是已有 key
就用 baseAssignValue
初始化其 value
为 1
,判断是已有 key
就将该 key
对应的 value
值加 1
var countBy = createAggregator(function(result, value, key) {
if (hasOwnProperty.call(result, key)) {
++result[key];
} else {
baseAssignValue(result, key, 1);
}
});
2. createAggregator
1. 传入参数
setter
对accumulator
累加器进行处理的函数,initalizer
初始化函数,初始化一个用于记录的累加器,默认是一个对象
2. 源码分析
创建了一个聚合器函数,这里的没有传入 initalizer
因此默认创建一个新对象作为数据的记录。使用 setter
计数
这里的聚合器函数内部判断了集合是否是 array
。是数组就用 arrayAggregator
,否则用 baseAggregator
这里我其实有个疑问,就是为什么不直接判断 isArrayLike
,这样就不需要后面 baseAggregator
再判断一次,而 arrayAggregator
完全可以胜任
function createAggregator(setter, initializer) {
return function(collection, iteratee) {
var func = isArray(collection) ? arrayAggregator : baseAggregator,
accumulator = initializer ? initializer() : {};
return func(collection, setter, getIteratee(iteratee, 2), accumulator);
};
}
3. getIteratee
1. 传入参数
value
需要被迭代器处理的值arity
所创建迭代对象的数量
2. 源码分析
获取 lodash.iteratee
,或者使用传入的 iteratee
,使用 lodash 的内置迭代器 iteratee
处理外部的迭代器,并将其返回,iteratee
这个函数是 lodash 的一个很大的亮点,也是 lodash 内最复杂的函数之一,以后会讲讲用法
function getIteratee() {
var result = lodash.iteratee || iteratee;
result = result === iteratee ? baseIteratee : result;
return arguments.length ? result(arguments[0], arguments[1]) : result;
}
4. arrayAggregator
1. 传入参数
array
普通数组setter
处理accumulator
的函数iteratee
迭代器,和countBy
的迭代器一致accumulator
累加器,记录最终结果
2. 源码分析
使用 setter
计数,accmulator
是用于计数的对象,key
值是 iteratee(value)
的返回值,最终返回累加器 accmulator
函数内部以 length
为条件,使用 while
循环
function arrayAggregator(array, setter, iteratee, accumulator) {
var index = -1,
length = array == null ? 0 : array.length;
while (++index < length) {
var value = array[index];
setter(accumulator, value, iteratee(value), array);
}
return accumulator;
}
5. baseAggregator
1. 传入参数
collection
类数组对象或是对象setter
处理accumulator
的函数iteratee
迭代器,和countBy
的迭代器一致accumulator
累加器,记录最终结果
2. 源码分析
baseEach
会判断 collection
是否是类数组,如果不是,会使用 createBaseFor
取出 collection
里所有的 key
及其总数,并进行 while
循环遍历所有的 key
取到 value
,最后返回计数器
function baseAggregator(collection, setter, iteratee, accumulator) {
baseEach(collection, function(value, key, collection) {
setter(accumulator, value, iteratee(value), collection);
});
return accumulator;
}
6. baseAssignValue
1. 传入参数
object
一个对象key
需要合并的key
值value
前一个参数key
值对应的value
值
2. 源码分析
利用原生对象上的 defineProperty
合并 __proto__
的属性,直接用 []
合并对象的其他值
function baseAssignValue(object, key, value) {
if (key == '__proto__' && defineProperty) {
defineProperty(object, key, {
'configurable': true,
'enumerable': true,
'value': value,
'writable': true
});
} else {
object[key] = value;
}
}
应用场景
类似如下,统计了数组里整数部分相同的数字,以及相同长度的字符串出现的次数
_.countBy([6.1, 4.2, 6.3], Math.floor);
// => { '4': 1, '6': 2 }
_.countBy(['one', 'two', 'three'], 'length');
// => { '3': 2, '5': 1 }
总结
countBy
函数通过传入 iteratee
能够根据一定的条件筛选出合适的 key
值补并进行计数,是函数式编程的一种展现。传统的统计方式可能就需要创建新的函数去专门遍历并使用 iteratee
,最后再传入 count
中去统计,而利用函数式编程的思想,可以利用 countBy
直接传入函数 iteratee
作为条件,直接用一个函数完成统计