前言
不知不觉已经写了 这么多的 lodash
了,今天是周二,带来groupBy,includes,invokeMap
Api
_.groupBy(collection, [iteratee=_.identity])
Creates an object composed of keys generated from the results of running each element of collection
thru iteratee
. The order of grouped values is determined by the order they occur in collection
. The corresponding value of each key is an array of elements responsible for generating the key. The iteratee is invoked with one argument: (value) .
Arguments
collection
(Array|Object) : The collection to iterate over.[iteratee=_.identity]
(Function) : The iteratee to transform keys.
Returns
(Object) : Returns the composed aggregate object.
Example
_.groupBy([6.1, 4.2, 6.3], Math.floor);
// => { '4': [4.2], '6': [6.1, 6.3] }
// The `_.property` iteratee shorthand.
_.groupBy(['one', 'two', 'three'], 'length');
// => { '3': ['one', 'two'], '5': ['three'] }
这个是对原数组进行分组,先使用 reduce
简单模拟一下
function groupBy2(collection,iterate){
// 如果是函数,不变
// 如果是字符串,格式化为 一个函数
var baseIterate = typeof iterate == "function" ? iterate : (obj)=>obj[iterate]
return collection.reduce((prev,cur)=>{
if(prev[baseIterate(cur)]){
prev[baseIterate(cur)].push(cur)
}else {
prev[baseIterate(cur)] = [cur]
}
return prev
},{})
}
在 lodash
中分成了多个函数来执行,来看一下 lodash
中是怎么表示的
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
var groupBy = createAggregator(function(result, value, key) {
// 如果有值,就使用 push
if (hasOwnProperty.call(result, key)) {
result[key].push(value);
} else {
// 没有值,就使用 restult[key] = [value]
object[key] = [value];
}
});
使用 createAggregator
创建一个聚合器
function createAggregator(setter) {
return function(collection, iteratee) {
// collection 用户传递的数组,iteratee 用户传来的 Math.floor 或者 "length"
var func = arrayAggregator,
accumulator = {};
// setter 是 传入 createAggregator 的内部函数
return func(collection, setter, baseIteratee(iteratee), accumulator);
};
}
重点在于 arrayAggregator
function arrayAggregator(array, setter, iteratee, accumulator) {
var index = -1,
length = array == null ? 0 : array.length;
while (++index < length) {
var value = array[index];
// 在这个地方,执行 createAggregator 的内部方法,
// result 原始值,格式化之后的原始值
// 对应的 createAggregator 内部的是 result, value, key
setter(accumulator, value, iteratee(value), array);
}
return accumulator;
}
辅助方法 baseIteratee
function baseIteratee(iteratee){
return typeof iteratee == "function" ? iteratee :(object)=> object[iteratee]
}
_.includes(collection, value, [fromIndex=0])
Checks if value
is in collection
. If collection
is a string, it’s checked for a substring of value
, otherwise SameValueZero
is used for equality comparisons. If fromIndex
is negative, it’s used as the offset from the end of collection
.
Arguments
collection
(Array|Object|string) : The collection to inspect.value
()* : The value to search for.[fromIndex=0]
(number) : The index to search from.
Returns
(boolean) : Returns true
if value
is found, else false
.
Example
_.includes([1, 2, 3], 1);
// => true
_.includes([1, 2, 3], 1, 2);
// => false
_.includes({ 'a': 1, 'b': 2 }, 1);
// => true
_.includes('abcd', 'bc');
// => true
inlcudes 可以的第一个参数可以接受 数组 /对象 /字符串,第二个参数是判断参数是 value 值,第三个参数是 从哪里开始的下标
source 简化版
function includes(collection, value, fromIndex){
// 如果是对象,只需要获取 对象的 values 属性
collection = isArrayLike(collection) ? collection : Object.values(collection);
var length = collection.length;
fromIndex = fromIndex ? fromIndex : 0;
// 如果 fromIndex为 负值,说明是从后往前
if (fromIndex < 0) {
fromIndex = Math.max(length + fromIndex, 0);
}
// indexOf 既可以判断 数组,也可以判断字符串
// includes 也可以
return collection.indexOf(value, fromIndex) > -1
}
isArrayLike
判断是否有length 属性,并且不能是函数,因为 函数也有 length 属性,只不过是形参数量
function isLength(value) {
return typeof value == 'number';
}
function isFunction(value) {
return typeof value == 'function';
}
function isArrayLike(value) {
return value != null && isLength(value.length) && !isFunction(value);
}
_.invokeMap(collection, path, [args])
Invokes the method at path
of each element in collection
, returning an array of the results of each invoked method. Any additional arguments are provided to each invoked method. If path
is a function, it’s invoked for, and this
bound to, each element in collection
.
Arguments
collection
(Array|Object) : The collection to iterate over.path
(Array|Function|string) : The path of the method to invoke or the function invoked per iteration.[args]
(…)* : The arguments to invoke each method with.
Returns
(Array) : Returns the array of results.
Example
_.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
// => [[1, 5, 7], [1, 2, 3]]
_.invokeMap([123, 456], String.prototype.split, '');
// => [['1', '2', '3'], ['4', '5', '6']]
source
源码比较复杂,做了一部分简化
invokeMap
以 [[5, 1, 7], [3, 2, 1]], 'sort'
//
function invokeMap(collection, path, ...args) {
var index = -1,
isFunc = typeof path == "function",
// 判断是否是一个数组
result = isArrayLike(collection) ? Array(collection.length) : [];
// isFunc 是 false
collection.forEach(value => {
result[++index] = isFunc
? path.apply(value, args)
: baseInvoke(value, path, args);
});
return result;
}
巧妙的找到原型方法
// object 是 [1,2,3] / [4,5,6]
// path 是 sort
// args 是 []
function baseInvoke(object, path, args) {
// []["sort"]
// 找到 数组原型链上的 sort 方法
var func = object == null ? object : object[path];
return func == null ? undefined : func.apply(object, args);
}
func.apply(object, args)
等同于为 []["sort"].apply([1,2,3],[])
同样的 invokeMap([123, 456], String.prototype.split, '')
, 由于 isFunc
是 true
,可以直接调用 path.apply(value.args)
,也就是 String.prototype.split.apply(123,[''])
在 apply 内部,如果发现 this 是一个基础类型,会包裹一层 Object
String.prototype.split.apply(123,[''])
= String.prototype.split.apply( Object(123),[''] )
结尾
其实还是有一些收获的,看源码越来越有心得,第一个就是先看大的方面,在刚开始的时候,只需要关注函数的返回值,不去细究函数内部,第二个是从简单的测试开始,先大致走通简单测试,大致理解思路,然后不断增加功能