0%

lodash源码解析:flatten家族

本篇分析下flatten家族的方法,包括flattenflattenDeepflattenDepthflatMapflatMapDeepflatMapDepth,最重要的是他们的核心方法baseFlatten。最后给出几个方法的原生实现。

核心方法 baseFlatten

六个flatten方法全都是封装的baseFlatten方法,这个方法主要是有四个比较特殊的参数:

  1. depth是扁平化的深度,也就是扁平化递归流程执行几次,在结果的体现上就是array的内部嵌套要解开几层。
  2. predicate是断言函数,目的是判断要不要对某元素执行扁平化。
  3. isStrict是如何处理断言函数判断为假的元素,isStrict为真的话就直接抛弃该元素,为假就保留该元素
  4. result是初始的结果数组,默认为空数组。
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';

/**
* `flatten`方法的基本方法,支持带约束条件的扁平化
*
* @private
* @param {Array} array 要扁平化的数组
* @param {number} depth 最大的递归深度
* @param {boolean} [predicate=isFlattenable] 每次迭代调用的断言函数
* @param {boolean} [isStrict] 仅限通过`predicate`断言函数检查的值。
* @param {Array} [result=[]] 初始的结果数组
* @returns {Array} 返回一个新的扁平化后的数组。
*/
function baseFlatten(array, depth, predicate, isStrict, result) {
// 给predicate和result置初始值
// 默认是 isFlattenable 和 []
predicate || (predicate = isFlattenable);
result || (result = []);

// array为null或undefined时,返回result
if (array == null) {
return result;
}

// 开始迭代
for (const value of array) {
// 深度大于0且可扁平化的value才执行下一步
if (depth > 0 && predicate(value)) {
if (depth > 1) {
// 递归展平数组(受到调用堆栈数限制)。
baseFlatten(value, depth - 1, predicate, isStrict, result);
} else {
// 达到深度或完全展平后就push到result中
result.push(...value);
}
// 不可扁平化的值就按原样赋值给结果数组(isStrict为假的情况下)
} else if (!isStrict) {
// 这里其实处理的是不能通过predicate检验的元素,当isStrict为true的时候,就直接舍弃
// 相当于push,不使用push是因为性能原因
// 参见https://segmentfault.com/q/1010000021808718
result[result.length] = value;
}
}
// 把最后的result数组返回
return result;
}

export default baseFlatten;

flatten 家族

flatten

flatten方法是执行的depth1baseFlatten方法,也就是说执行一次扁平化处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import baseFlatten from './.internal/baseFlatten.js';

/**
* 使`array`扁平化一层深度
*
* @since 0.1.0
* @category Array
* @param {Array} array 要扁平化的数组
* @returns {Array} 返回扁平化后的新数组
* @see flatMap, flatMapDeep, flatMapDepth, flattenDeep, flattenDepth
* @example
*
* flatten([1, [2, [3, [4]], 5]])
* // => [1, 2, [3, [4]], 5]
*/
function flatten(array) {
// 先取到数组的长度
const length = array == null ? 0 : array.length;
// 调用baseFlatten,传参1
return length ? baseFlatten(array, 1) : [];
}

export default flatten;

flattenDeep

flattenDeep方法是执行的depth无限大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
import baseFlatten from './.internal/baseFlatten.js';

/** 用于多种`Number`类型常量的引用 */
const INFINITY = 1 / 0;

/**
* 递归的扁平化`array`
*
*
* @since 3.0.0
* @category Array
* @param {Array} array 要扁平化的数组
* @returns {Array} 返回新的扁平化后的数组
* @see flatMap, flatMapDeep, flatMapDepth, flatten, flattenDepth
* @example
*
* flattenDeep([1, [2, [3, [4]], 5]])
* // => [1, 2, 3, 4, 5]s
*/
function flattenDeep(array) {
// 先取到length
const length = array == null ? 0 : array.length;
// 同样是调用baseFlatten,但是传参1 / 0
return length ? baseFlatten(array, INFINITY) : [];
}

export default flattenDeep;

flattenDepth

flattenDeep方法是执行的depth给定值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
import baseFlatten from './.internal/baseFlatten.js';

/**
* 固定深度的递归扁平化`array`
*
* @since 4.4.0
* @category Array
* @param {Array} array 要扁平化的数组
* @param {number} [depth=1] 最大递归深度
* @returns {Array} 返回新的扁平化后的数组
* @see flatMap, flatMapDeep, flatMapDepth, flattenDeep
* @example
*
* const array = [1, [2, [3, [4]], 5]]
*
* flattenDepth(array, 1)
* // => [1, 2, [3, [4]], 5]
*
* flattenDepth(array, 2)
* // => [1, 2, 3, [4], 5]
*/
function flattenDepth(array, depth) {
// 获取array的length
const length = array == null ? 0 : array.length;
if (!length) {
return [];
}
// +depth是转化为数字格式
depth = depth === undefined ? 1 : +depth;
// 调用baseFlatten,传参depth
return baseFlatten(array, depth);
}

export default flattenDepth;

flatMap

flatMapflatten的区别是flatMap方法要先执行map,使用iteratee将每个元素进行处理后,再进行扁平化。

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

/**
* 创建一个扁平化后的数组,
* 这个数组的值来自collection(集合)中的每一个值经过 iteratee(迭代函数) 处理后返回的结果,
* 并且扁平化合并。 iteratee 调用三个参数: (value, index|key, collection)。
*
* @since 4.0.0
* @category Collection
* @param {Array|Object} collection 用来迭代遍历的集合
* @param {Function} iteratee 每次迭代调用的函数
* @returns {Array} 返回扁平化后的新数组
* @see flatMapDeep, flatMapDepth, flatten, flattenDeep, flattenDepth, map, mapKeys, mapValues
* @example
*
* function duplicate(n) {
* return [n, n]
* }
*
* flatMap([1, 2], duplicate)
* // => [1, 1, 2, 2]
*/
function flatMap(collection, iteratee) {
// 先用map遍历集合,迭代collection
// 再调用baseFlatten,传参1
return baseFlatten(map(collection, iteratee), 1);
}

export default flatMap;

flatMapDeep

详见flattenDeepflatMap

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

/** 用于多种`Number`类型常量的引用 */
const INFINITY = 1 / 0;

/**
* 此方法类似`flatMap`,但是会递归的扁平化map后的结果。
*
* @since 4.7.0
* @category Collection
* @param {Array|Object} collection 要迭代的集合
* @param {Function} iteratee 每次迭代调用的函数
* @returns {Array} 返回扁平化后的新数组
* @see flatMap, flatMapDepth, flatten, flattenDeep, flattenDepth, map, mapKeys, mapValues
* @example
*
* function duplicate(n) {
* return [[[n, n]]]
* }
*
* flatMapDeep([1, 2], duplicate)
* // => [1, 1, 2, 2]
*/
function flatMapDeep(collection, iteratee) {
// map用来迭代集合
// 调用baseFlatten并传参1 / 0,无限递归迭代
return baseFlatten(map(collection, iteratee), INFINITY);
}

export default flatMapDeep;

flatMapDepth

详见flattenDepthflatMap

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

/**
* 此方法类似`flatMap`,但是会按照`depth`固定深度的扁平化map后的数组
*
* @since 4.7.0
* @category Collection
* @param {Array|Object} collection 要迭代的集合
* @param {Function} iteratee 每次迭代调用的函数
* @param {number} [depth=1] 递归的最大的深度
* @returns {Array} 返回新的扁平化后的数组
* @see flatMap, flatMapDeep, flatten, flattenDeep, flattenDepth, map, mapKeys, mapValues
* @example
*
* function duplicate(n) {
* return [[[n, n]]]
* }
*
* flatMapDepth([1, 2], duplicate, 2)
* // => [[1, 1], [2, 2]]
*/
function flatMapDepth(collection, iteratee, depth) {
// 先获取深度
// +depth强制转化为Number类型
depth = depth === undefined ? 1 : +depth;
// map用来迭代集合
// 调用baseFlatten并传参depth,按固定深度递归扁平化
return baseFlatten(map(collection, iteratee), depth);
}

export default flatMapDepth;

原生写法

最新的规范和实现,已经支持了原生扁平化处理。

Array.prototype.flat()

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]

let arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

let arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]

//使用 Infinity,可展开任意深度的嵌套数组
let arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Array.prototype.flatMap()

flatMap() 方法首先使用map函数映射每个元素,然后将结果扁平化成一个新数组。它与 map 连着深度值为 1 的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。

1
2
3
4
5
6
7
8
9
10
11
let arr1 = [1, 2, 3, 4];

arr1.map((x) => [x * 2]);
// [[2], [4], [6], [8]]

arr1.flatMap((x) => [x * 2]);
// [2, 4, 6, 8]

// 只能扁平化一层
arr1.flatMap((x) => [[x * 2]]);
// [[2], [4], [6], [8]]

flatMapDeep

原生的 flatMap 只能展开一层,下面利用原生方法实现完全扁平化,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const flatMapDeep = (arr, iteratee) => {
return arr.flatMap((subArray, index, array) => {
// 第一层递归时,执行下map映射函数
subArray = iteratee != null ? iteratee(subArray, index, array) : subArray;
// 当元素依然是数组时,继续对元素调用flatMapDeep,
// 但是iteratee传参为null,所以不再执行map映射函数
// 不再是数组时,说明展开到底,就直接返回subArray
return Array.isArray(subArray) ? flatMapDeep(subArray, null) : subArray;
});
};

flatMapDeep([1, [[2], [3, [4]], 5]]);
// => [1, 2, 3, 4, 5]

flatMapDeep([1, 2, 3], (x) => [[x, x]]);
// => [1, 1, 2, 2, 3, 3]
👆 全文结束,棒槌时间到 👇