0%

lodash源码解析:intersection家族

本篇分析下intersection家族的方法,包括intersectionintersectionByintersectionWith以及核心方法baseIntersection。最后给出intersection方法的原生实现。

核心方法 baseIntersection

三个flatten方法全都是封装的baseIntersection方法,这个方法有三个参数:

  1. arrays是需要求交集的数组(array)组成的数组(arrays)。
  2. iterateearray中每个元素都要执行的迭代函数,执行完再进行求交集,主要服务给intersectionBy用。
  3. comparatorarray中每个元素都要执行的比较器函数,用于设定如何比较的规则,主要服务给intersectionWith用。
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
import SetCache from './SetCache.js';
import arrayIncludes from './arrayIncludes.js';
import arrayIncludesWith from './arrayIncludesWith.js';
import map from '../map.js';
import cacheHas from './cacheHas.js';

/**
* `intersection`家族方法的基础实现,接受要检查的array组成的arrays
*
* @private
* @param {Array} arrays 要检查的arrays
* @param {Function} [iteratee] 每个元素调用的iteratee函数
* @param {Function} [comparator] 每个元素调用的比较器函数
* @returns {Array} 返回包含所有传入数组交集元素的新数组。
*/
function baseIntersection(arrays, iteratee, comparator) {
// 当比较器函数存在时,includes设为arrayIncludesWith,否则设为arrayIncludes
const includes = comparator ? arrayIncludesWith : arrayIncludes;
// arrays中第一个array的长度(因为是以第一个为基础)
const length = arrays[0].length;
// 整个arrays的长度
const othLength = arrays.length;
// 定义一个与arrays等长的缓存数组
const caches = new Array(othLength);
// 初始化返回结果
const result = [];

let array;
let maxLength = Infinity;
let othIndex = othLength;

// 从右向左迭代
while (othIndex--) {
// array来放置迭代时arrays中的每个array
array = arrays[othIndex];
if (othIndex && iteratee) {
// 当iteratee存在时,将array中的每个元素转换下
array = map(array, (value) => iteratee(value));
}
// 防止数组过长
maxLength = Math.min(array.length, maxLength);
// 下面的条件是,比较器函数不存在,并且(iteratee函数存在或者数组过长)
caches[othIndex] =
!comparator && (iteratee || (length >= 120 && array.length >= 120))
? // 就会用到效率较高的SetCache
new SetCache(othIndex && array)
: undefined;
}
// 重新把array设为处理后的arrays的第一个参数
array = arrays[0];

let index = -1;
const seen = caches[0];
// 处理到这一步时,caches中充满了length比较长的array和对应的键
outer: while (++index < length && result.length < maxLength) {
let value = array[index];
// 这一步就是对第一个array进行简单的iteratee处理
const computed = iteratee ? iteratee(value) : value;

value = comparator || value !== 0 ? value : 0;
// 下面就是检查cache中是否存在,相同的,存在就push到结果里
if (
!(seen
? cacheHas(seen, computed)
: includes(result, computed, comparator))
) {
othIndex = othLength;
while (--othIndex) {
const cache = caches[othIndex];
if (
!(cache
? cacheHas(cache, computed)
: includes(arrays[othIndex], computed, comparator))
) {
continue outer;
}
}
if (seen) {
seen.push(computed);
}
result.push(value);
}
}
return result;
}

export default baseIntersection;

intersection 家族

intersection

intersection方法是普通的求交集方法。

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 map from './map.js';
import baseIntersection from './.internal/baseIntersection.js';
import castArrayLikeObject from './.internal/castArrayLikeObject.js';

/**
* 创建唯一值的数组,这个数组包含所有给定数组都包含的元素,
* 使用 [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* 顺序和结果值的引用由第一个数组决定
* 进行相等性比较。(注:可以理解为给定数组的交集)
*
* @since 0.1.0
* @category Array
* @param {...Array} [arrays] 要检查的`arrays`
* @returns {Array} 返回一个包含所有传入数组交集元素的新数组。
* @example
*
* intersection([2, 1], [2, 3])
* // => [2]
*/
function intersection(...arrays) {
// 先使用map方法将所有的参数转化为对应的类数组对象,避免传入的参数不正确
const mapped = map(arrays, castArrayLikeObject);
// 当mapped的length存在且第一个参数为类数组对象时(其实还是避免传入参数不正确)
return mapped.length && mapped[0] === arrays[0]
? // 调用基础的baseIntersection(mapped)方法
baseIntersection(mapped)
: // 否则返回空数组
[];
}

export default intersection;

intersectionBy

intersectionBy方法是求交集前先将每个元素处理一遍。

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
import map from './map.js';
import baseIntersection from './.internal/baseIntersection.js';
import castArrayLikeObject from './.internal/castArrayLikeObject.js';
import last from './last.js';

/**
* 这个方法类似 `intersection`,区别是它接受一个 `iteratee`参数,
* 用于调用每一个`arrays`中的每个元素以产生一个值,通过产生的值进行比较。
* 结果值是从第一数组中选择。
* iteratee 会传入一个参数:(value)。
*
* @since 4.0.0
* @category Array
* @param {...Array} [arrays] 要检查的`arrays`
* @param {Function} iteratee iteratee调用每一个元素
* @returns {Array} 返回一个包含所有传入数组交集元素的新数组。
* @example
*
* intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor)
* // => [2.1]
*/
function intersectionBy(...arrays) {
// 取到最后一个参数,即为iteratee
let iteratee = last(arrays);
// 同样将arrays中的所有参数转为类数组对象
const mapped = map(arrays, castArrayLikeObject);

// 判断到底传没传iteratee参数,也就是最后一项经过类型转换后和以前是否还一致。
if (iteratee === last(mapped)) {
// 一致,说明没传iteratee参数
iteratee = undefined;
} else {
// 不一致,说明传了iteratee参数,则从mapped中弹出最后一个参数
mapped.pop();
}
// 同intersection一样,还是避免传入参数不正确
return mapped.length && mapped[0] === arrays[0]
? // 同样是调用基础方法baseIntersection,但是多传一个iteratee参数
baseIntersection(mapped, iteratee)
: [];
}

export default intersectionBy;

intersectionWith

intersectionWith方法是求交集时选用不同的规则比较。

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
import map from './map.js';
import baseIntersection from './.internal/baseIntersection.js';
import castArrayLikeObject from './.internal/castArrayLikeObject.js';
import last from './last.js';

/**
*
* 这个方法类似 `intersection`,区别是它接受一个 `comparator` ,用于调用比较`arrays`中的每个元素。
* 结果值是从第一数组中选择。
* comparator 会传入两个参数:(arrVal, othVal)。
*
* @since 4.0.0
* @category Array
* @param {...Array} [arrays] 要检查的数组
* @param {Function} [comparator] 调用每一个元素的比较器
* @returns {Array} 返回一个包含所有传入数组交集元素的新数组。
* @example
*
* const objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
* const others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]
*
* intersectionWith(objects, others, isEqual)
* // => [{ 'x': 1, 'y': 2 }]
*/
function intersectionWith(...arrays) {
// 取到最后一个参数,即为iteratee
let comparator = last(arrays);
// 同样将arrays中的所有参数转为类数组对象
const mapped = map(arrays, castArrayLikeObject);

// 这里选择的是与intersectionBy不同的判断方式,直接判断typeOf是否为function
comparator = typeof comparator === 'function' ? comparator : undefined;
// 存在comparator时,直接弹出最后一个参数
if (comparator) {
mapped.pop();
}

// 同intersection一样,还是避免传入参数不正确
return mapped.length && mapped[0] === arrays[0]
? // 同样是调用基础方法baseIntersection,但是多传一个comparator参数
baseIntersection(mapped, undefined, comparator)
: [];
}

export default intersectionWith;

原生写法

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

1
2
3
4
5
6
7
8
9
10
11
12
let arrays = [
[1, 2, 3],
[101, 2, 1, 10],
[2, 1],
];
// reduce不设置initialValue时,默认第一个accumulator为array的第一个元素
console.log(
arrays.reduce((accumulator, currentValue) =>
accumulator.filter((item) => currentValue.includes(item))
)
);
// [1, 2]
👆 全文结束,棒槌时间到 👇