引言
先附上 lodash 的中文网址:lodash.shujuwajue.com/
这很重要,因为英文网址的光看方法名字可能不知道其作用,而中文网址帮我们简单地描述了每个方法对应的作用,这很nice!
另外,附上一些其它的学习资料:
groupBy
lodash 库里面关于 groupBy 的源码是这样的:
function groupBy(collection, iteratee) {
return reduce(collection, (result, value, key) => {
key = iteratee(value)
if (hasOwnProperty.call(result, key)) {
result[key].push(value)
} else {
baseAssignValue(result, key, [value])
}
return result
}, {})
}
这里有个小疑问,iteratee 如果是 string,它是怎么成功运行 iteratee(value) 的呢?
关于这一点, groupBy 里面的 iteratee 的由来(简要链路:collection 为一维数组的情况下是这样的):
getIteratee -> baseIteratee = (value) => { return baseProperty(...) }
下面是 baseProperty 的实现:
function baseProperty(key) {
return function(object) {
return object == null ? undefined : object[key];
};
}
createAggregator
此为 lodash 的内部 reduce 方法,看一下源码:
/**
* Creates a function like `_.groupBy`.
*
* @private
* @param {Function} setter The function to set accumulator values.
* @param {Function} [initializer] The accumulator object initializer.
* @returns {Function} Returns the new aggregator function.
*/
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);
};
}
iteratee
iteratee 目前个人理解仅局限于数组的应用:
// 例 1-1
var users = [
{ 'user': 'barney', 'age': 36, 'active': true },
{ 'user': 'fred', 'age': 40, 'active': false }
];
// The `_.property` iteratee shorthand.
_.map(users, _.iteratee('user'))
// => ['barney', 'fred']
iteratee 是循环里边调用的一个函数,如果这个数据源是数组的话,那么 iteratee 的形参就是当次循环里的 item:
baseIteratee = (value) => { return baseProperty(...) }
function baseIteratee(value) {
if (typeof value == 'function') {
return value;
}
if (value == null) {
return identity;
}
// 针对对象 or 数组,数组则使用 baseMatchesProperty,
// 对象则使用 baseMatches
if (typeof value == 'object') {
return isArray(value)
? baseMatchesProperty(value[0], value[1])
: baseMatches(value);
}
return property(value);
}
function baseProperty(key) {
return function(object) {
return object == null ? undefined : object[key];
};
}
所以例 1-1 的 iteratee 可以认为是 _.property,如官网说的。
但 iteratee 的奇妙之处不止于此,看看它的其他用法,还是上面的 例 1-1:
// The `_.matches` iteratee shorthand.
_.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
// => [{ 'user': 'barney', 'age': 36, 'active': true }]
用了上面的写法,iteratee 又秒变成 _.matches 了,真是太 awesome 了!
property
function baseProperty(key) {
return function(object) {
return object == null ? undefined : object[key];
};
}
matches
调用 matches 会较大概率命中 baseIsMatch:
// object 是遍历的对象
// source 是 matches 传入的参数
// matchData 是 getMatchData 的返回值,
// 形如: Array([key, value, isStrictComparable(value)])
function baseIsMatch(object, source, matchData, customizer)
baseIsMatch 会对 source 的 value 和 object 的 value 做比对,目前只看了这么多。。。
matchesProperty
function baseMatchesProperty(path, srcValue) {
if (isKey(path) && isStrictComparable(srcValue)) {
return matchesStrictComparable(toKey(path), srcValue);
}
return function(object) {
var objValue = get(object, path);
return (objValue === undefined && objValue === srcValue)
? hasIn(object, path)
: baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG);
};
}
identity
identity 的源码如下:
function identity(value) {
return value;
}
当 iteratee 作为功能函数参数未传值时(如:groupBy(array)),默认就为 identity
为什么会设计 identity 呢?(先留下这个疑问)
个人解答:可能是因为 iteratee 在内部都是以函数形式调用,而 iteratee 在外部未被定义时,identity 就能充当一个垫底的函数,保证内部 iteratee 调用正常执行。
删除数组中元素(remove, pullAllBy, pullAllWith, pull, pullAll, pullAt)
总结:删除数组中元素的方法都会改变原数组。
remove 很好理解,看下它的声明:
_.remove(array, [predicate=_.identity])
删除满足 predicate 的元素,官网例子:
var array = [1, 2, 3, 4];
var evens = _.remove(array, function(n) {
return n % 2 == 0;
});
console.log(array);
// => [1, 3]
console.log(evens);
// => [2, 4]
pullAllBy
先看下声明:
_.pullAllBy(array, values, [iteratee=_.identity])
官网例子:
var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
_.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');
console.log(array);
// => [{ 'x': 2 }]
简单理解:如果需要删除数组里多个特征的元素,如上例的删除 x = 1 || x = 3 的元素,用 pullAllBy 可以比较方便地写出来。(当然 remove 也能实现,只是要写逻辑)
关键字:或(OR)逻辑,可以使用 pullAllBy。
pullAllWith
先看下声明:
_.pullAllWith(array, values, [comparator])
官网例子:
var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }];
_.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual);
console.log(array);
// => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]
简单理解:如果需要删除数组里同时满足多个特征的元素,如上例的删除 x = 3 && y = 4 的元素,用 pullAllWith 可以比较方便地写出来。(当然 remove 也能实现,只是要写逻辑)
关键字:与(AND)逻辑,可以使用 pullAllWith。
pull && pullAll
两者功能一样,传参方式不同,有点像数组的 apply 和 call 方法。
作用都是删除数组指定元素:
var array = ['a', 'b', 'c', 'a', 'b', 'c'];
_.pull(array, 'a', 'c');
// _.pullAll(array, ['a', 'c']);
console.log(array);
// => ['b', 'b']
pullAt
删除指定索引集的元素:
// 声明
_.pullAt(array, [indexes])
var array = ['a', 'b', 'c', 'd'];
var pulled = _.pullAt(array, [1, 3]);
console.log(array);
// => ['a', 'c']
console.log(pulled);
// => ['b', 'd']
第二用途:(根据下标选择元素,分到两个数组)
By 和 With
通常带 By 的方法都是接收传入一个 iteratee,而带 With 的方法都是接收一个 comparator。
iteratee 和 comparator 的形参需要根据上下文而定(看实际调用的方法)。通常的 iteratee 的声明应是这样的:
iteratee(value, index, array)
comparator 的声明通常如下:
comparator(value, computed)
computed 就是要与 value 对比的值,如果在一个可迭代集合调用一个 comparator,比如:_.isEqual,那么 computed 通常就是特定循环内的 value。
isEqual
isEqual 很强大,可以比较 Object 的值是否相等,巨方便,巨实用。通常作为 comparator 被应用于其他主功能方法,如:_.uniqWith
数组去重(uniq, uniqBy, uniqWith, sortedUniq, sortedUniqBy)
真正能实现去重的只有 uniq, uniqBy, uniqWith,而带上 sorted 的 Uniq 方法,排序也实现不了,去重也实现不了(如:[2, 1, 2],输出 [2, 1, 2]),很神奇。。。
sortBy
翻看源码时,偶然看到了这个(获取函数的形参个数,还有日常的获取函数的实参个数):
// 获取函数形参个数
func.length
// 获取函数实参个数
arguments.length
在说 sortBy 之前,先说说 js 原生的 sort 方法,因为 sortBy 的底层实现也是用了原生 js 数组的 sort 方法。
sort 在不传入参数的情况下,默认会对数组内的非 undefined 的元素进行升序排序,而且会默认转化成字符串进行排序,也就是说,如果有一个数组:[80, 9],那么调用了 sort 以后,结果应为:[80, 9]。因为数字被转化成字符串去进行排序了。
const arr = [80, 9]
arr.sort()
// [80, 9]
一般排序数字会用到:
function compareNumbers(a, b) {
return a - b;
}
a - b 意味着升序,反之,b - a 意味着降序
一般排序字符串(英文)会用到:
a.localeCompare(b)
使用了 localeCompare 可以有效的排序大小写,使程序更稳健。
**注意 :**按localeCompare来比较中文会有bug:多音字的问题,长沙,重庆这种,系统取的是zhang和zhong的拼音。所以导致排序时这种字会垫底。
关于 sort 的排序是怎么实现的,可以了解一下这篇:从 V8 源码看 JS 数组排序的诡异问题
好,回归主题,sortBy。sortBy 的 iteratee 可以传多个,正如它的定义:
// 注意这里的第二个参数是 iteratees,意味着可以传多个 iteratee,当然,传1个也能用
_.sortBy(collection, [iteratees=[_.identity]])
整理一下 sortBy 的思路:
先根据 collection 和 iteratees 整理出一份 result:
interface Result {
// 伪代码,表示 criteria 为 iteratee(item) 的返回值组成的数组
criteria: ReturnType<iteratee(item)>[]
index: number
value: item
}
declare const result: Result[]
拿到了 result 之后,调动原生 js 的 sort,并传入自己定义的 compareMultiple,如果没传入 orders 的话(实际上 sortBy 并不能传入 orders,要用 orderBy 才可以传入 orders, orderBy 为 sortBy 的高配版),compareMultiple 默认按升序排序处理(compareAscending)。
function compareMultiple(object, other, orders) {
var index = -1,
objCriteria = object.criteria,
othCriteria = other.criteria,
length = objCriteria.length;
while (++index < length) {
var result = compareAscending(objCriteria[index], othCriteria[index]);
...
return result
}
}
可以看到,lodash 的比值选用的就是 criteria(iteratee 返回的值)。而 compareAscending 可以简单理解为如下函数:
// 简化版的 compareAscending,源码的 compareAscending 更规范,
// 逻辑更严谨,但大体逻辑无非如下:
function simpleCompareAscending(a, b) {
if (a > b) {
return 1;
}
if (a < b) {
return -1;
}
return 0;
}
看完之后,是否顿然大悟,原来大 lodash 的排序方法用的也是原生的 sort 方法,且是一样的套路。只不过 lodash 做了封装,原本的 compareFn 的形参对比的是 a, b,现在,经过改装,compareFn 对比的是 iteratee(a), iteratee(b)。而且 interatee 还可以传入多个,给人更多的操作空间,让程序编写更自由。
partition
可将一个集合分成两组元素数组,第一组包含 predicate 返回 truthy 的元素,第二组包含 predicate 返回为 falsey 的元素,示例:
var users = [
{ 'user': 'barney', 'age': 36, 'active': false },
{ 'user': 'fred', 'age': 40, 'active': true },
{ 'user': 'pebbles', 'age': 1, 'active': false }
];
_.partition(users, 'active');
// [['fred'], ['barney', 'pebbles']]
countBy
有统计符合某种特征的元素个数需求时,会用到:
_.countBy([6.1, 4.2, 6.3], Math.floor);
// => { '4': 1, '6': 2 }
实战场景
有两个数组,A根据B来排序
const arr = [{ label: 'giao', id: 1 }, { label: 'giao2', id: 2 }]
const arr2 = [{ label: 'giao2', id: 2 }, { label: 'giao', id: 1 }]
// 求 arr 根据 arr2 来排序
// 预期结果:
// [{ label: 'giao2', id: 2 }, { label: 'giao', id: 1 }]
// 解法:
const expectSort = arr2.map((item) => item.id)
const result = _.sortBy(arr, (item) => _.findIndex(expectSort, (id) => item.id === id))
// 原生解法:
arr.sort((a, b) => {
return expectSort.indexOf(a.id) - expectSort.indexOf(b.id)
})
判断一组 ids 是否有目标数组里没有的 id
const ids = [1, 2]
const arr = [{ id: 0, value: 0 }, { id: 1, value: 1 }]
// 如题
// 预期结果:
// false
const arrIds = arr.map(item => item.id);
const diff = _.difference(ids, arrIds);
if (diff.length > 0) {
console.log(`The following IDs are not present in the target array: ${diff.join(', ')}`);
}