本篇继续分析下 uniq
和 union
家族的方法,uniq
方法主要是将数组去重,union
方法主要是将多个数组聚合成一个数组并去重。包括uniq
、uniqBy
、uniqWith
、union
、unionBy
、unionWith
、以及核心方法baseUniq
、bashFlatten
。并说明下在 lodash
中by
、with
这两个后缀的区别。
对应源码分析已推到 github
仓库: https://github.com/MageeLin/lodash-source-code-analysis
uniq
和 union
家族方法的依赖路径图如下所示,其实只用看左半部分,右半部分的代码已经在之前的文章中分析过。

lodash 中的后缀
在 lodash
的方法中,同一家族的方法都是用后缀来区别开的,比如本篇中的 uniqBy
、uniqWith
。仔细翻了下所有的方法,最常用的后缀就是下表这几个:
后缀 |
意义 |
by |
iteratee(value) ,迭代方法 |
with |
comparator(arrVal, othVal) ,比较方法 |
while |
predicate(value, index, array) ,断言方法 |
last |
fromRight ,指示是否从右向左 |
right |
fromRight ,指示是否从右向左 |
deep |
depth 或CLONE_DEEP_FLAG ,指示是否深度运算 |
这里面大部分的后缀还是很容易看明白它的作用的,但是迭代方法 by
和比较方法 with
就很难分明白。多分析了几个方法,把自己的理解分享下。
就拿 uniq
方法举例,同样都是去重,uniqBy
和 uniqWith
的官方示例如下:
1 2 3 4 5 6 7 8 9 10 11 12
| uniqBy([2.1, 1.2, 2.3], Math.floor);
const objects = [ { x: 1, y: 2 }, { x: 2, y: 1 }, { x: 1, y: 2 }, ]; uniqWith(objects, isEqual);
|
很显然,uniqBy
的示例中,是把数组中所有的元素都向下取整后进行了对应的去重,也就是说挨个比较了 Math.floor(2.1)
、Math.floor(1.2)
、Math.floor(2.3)
,相同
的就对应去重.
而在 uniqWith
中,是调用了 isEqual
进行的两两比较,也就是说挨个比较 isEqual(objects[0], objects[1])
、isEqual(objects[0], objects[2])
、isEqual(objects[1], objects[2])
,返回true
的就对应去重。
所以在我看来,with
是可以完全实现所有的 by
的,比如 uniqBy
的官方示例可以使用 uniqWith
来实现。
1 2 3 4 5 6 7 8 9 10
| uniqBy([2.1, 1.2, 2.3], Math.floor);
uniqWith( [2.1, 1.2, 2.3], (arrVal, othVal) => Math.floor(arrVal) === Math.floor(othVal) );
|
uniq 和 union 家族
将 uniq
和 union
家族放在一起分析,是因为 union
的实现是利用的先将多个数组展平,然后再去重来实现的,所以 union
家族的三个方法都依赖到了 baseUniq
。
依赖的内部方法
setToArray
把 set
转化为 array
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
function setToArray(set) { let index = -1; const result = new Array(set.size);
// 不断的把set的值push给result set.forEach((value) => { result[++index] = value; }); return result; }
export default setToArray;
|
createSet
创建一个 set
,主要是在创建 set
做了兼容性的判断,很巧妙。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import setToArray from './setToArray.js';
const INFINITY = 1 / 0;
const createSet = Set && 1 / setToArray(new Set([, -0]))[1] == INFINITY ? (values) => new Set(values) : () => {};
export default createSet;
|
baseUniq
baseUniq
实现时也进行了长数组的优化,如果是手动实现可以不用这么复杂,根本原理就是双层的迭代。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| import SetCache from './SetCache.js'; import arrayIncludes from './arrayIncludes.js'; import arrayIncludesWith from './arrayIncludesWith.js'; import cacheHas from './cacheHas.js'; import createSet from './createSet.js'; import setToArray from './setToArray.js';
const LARGE_ARRAY_SIZE = 200;
function baseUniq(array, iteratee, comparator) { let index = -1; let includes = arrayIncludes; let isCommon = true;
const { length } = array; const result = []; let seen = result;
if (comparator) { isCommon = false; includes = arrayIncludesWith; } else if (length >= LARGE_ARRAY_SIZE) { const set = iteratee ? null : createSet(array); if (set) { return setToArray(set); } isCommon = false; includes = cacheHas; seen = new SetCache(); } // 普通模式下,就指向result else { seen = iteratee ? [] : result; }
outer: while (++index < length) { let value = array[index]; const computed = iteratee ? iteratee(value) : value;
value = comparator || value !== 0 ? value : 0;
if (isCommon && computed === computed) { let seenIndex = seen.length; while (seenIndex--) { if (seen[seenIndex] === computed) { continue outer; } } if (iteratee) { seen.push(computed); } result.push(value); } else if (!includes(seen, computed, comparator)) { if (seen !== result) { seen.push(computed); } result.push(value); } } return result; }
export default baseUniq;
|
baseFlatten
baseFlatten
之前解析过,不赘述它的依赖了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import isFlattenable from './isFlattenable.js';
function baseFlatten(array, depth, predicate, isStrict, result) { predicate || (predicate = isFlattenable); result || (result = []);
if (array == null) { return result; }
for (const value of array) { if (depth > 0 && predicate(value)) { if (depth > 1) { baseFlatten(value, depth - 1, predicate, isStrict, result); } else { result.push(...value); } } else if (!isStrict) { result[result.length] = value; } } return result; }
export default baseFlatten;
|
uniq 家族
uniq
家族的实现都是利用的 baseUniq
,分别是调用如下方法:
baseUniq(array)
baseUniq(array, iteratee)
baseUniq(array, undefined, comparator)
。
uniq
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import baseUniq from './.internal/baseUniq.js';
function uniq(array) { return array != null && array.length ? baseUniq(array) : []; }
export default uniq;
|
uniqBy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import baseUniq from './.internal/baseUniq.js';
function uniqBy(array, iteratee) { return array != null && array.length ? baseUniq(array, iteratee) : []; }
export default uniqBy;
|
uniqWith
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import baseUniq from './.internal/baseUniq.js';
function uniqWith(array, comparator) { comparator = typeof comparator === 'function' ? comparator : undefined; return array != null && array.length ? baseUniq(array, undefined, comparator) : []; }
export default uniqWith;
|
union 家族
union
家族的实现都是利用的 baseUniq
和 baseFlatten
,分别是调用如下方法:
baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true))
baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), iteratee)
baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator)
union
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import baseFlatten from './.internal/baseFlatten.js'; import baseUniq from './.internal/baseUniq.js'; import isArrayLikeObject from './isArrayLikeObject.js';
function union(...arrays) { return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true)); }
export default union;
|
unionBy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import baseFlatten from './.internal/baseFlatten.js'; import baseUniq from './.internal/baseUniq.js'; import isArrayLikeObject from './isArrayLikeObject.js'; import last from './last.js';
function unionBy(...arrays) { let iteratee = last(arrays); if (isArrayLikeObject(iteratee)) { iteratee = undefined; } return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), iteratee); }
export default unionBy;
|
unionWith
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import baseFlatten from './.internal/baseFlatten.js'; import baseUniq from './.internal/baseUniq.js'; import isArrayLikeObject from './isArrayLikeObject.js'; import last from './last.js';
function unionWith(...arrays) { let comparator = last(arrays); comparator = typeof comparator === 'function' ? comparator : undefined; return baseUniq( baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator ); }
export default unionWith;
|