lodash 版本 4.17.20
前言
flattenDeep 是用于展平多维数组或内嵌数组的数组工具函数,lodash 实现这个简单的函数的时候,为了其复用代码而用了很多巧妙的思路,比如使用了 Infinity 的特性,使得 flattenDeep 、flatten、union 等函数都可以共用一份代码
为了代码的健壮性,flattenDeep 会判断属性 Symbol.isConcateSpreadable ,这个属性控制对象是否可以被合并,即定义调用 concat() 函数时的行为,因此 flattenDeep 也可以对类数组对象使用
思路分析
源码分析
1. flattenDeep
- 传入参数分析
array目标数组,被展平的数组或类数组对象
- 源码分析
传入数组,并传入
Infinity作为展平层数
function flattenDeep(array) {
var length = array == null ? 0 : array.length;
return length ? baseFlatten(array, INFINITY) : [];
}
2. baseFlatten
- 传入参数分析
array目标数组,被展平的数组或类数组对象depth展平的层数,有多少层就会递归展平多少次predicate判断是否可行,默认isFlattenable,判断是否可展平isStrict限制通过predicate函数的值,准备以后说到union函数时再详细分析result递归结果,可以在多次递归中传递形成最终结果
- 源码分析
function baseFlatten(array, depth, predicate, isStrict, result) {
var index = -1,
length = array.length;
predicate || (predicate = isFlattenable);
result || (result = []);
while (++index < length) {
var value = array[index];
if (depth > 0 && predicate(value)) {
if (depth > 1) {
// Recursively flatten arrays (susceptible to call stack limits).
baseFlatten(value, depth - 1, predicate, isStrict, result);
} else {
arrayPush(result, value);
}
} else if (!isStrict) {
result[result.length] = value;
}
}
return result;
}
初始化 index 和 length,处理 value 的函数 predicate 默认是 isFlattenable
var index = -1,
length = array.length;
predicate || (predicate = isFlattenable);
result || (result = []);
判断 value 是否符合要求,默认情况即 predicate(默认 isFlattenable) 返回值是否为 true,判断是否可以展平,如果是数组、类数组对象 arguments,或是有 Symbol.isConcatSpreadable 属性,就可以被展平
// ...
while (++index < length) {
var value = array[index];
if (depth > 0 && predicate(value)) {
// ...
} else if (!isStrict) {
result[result.length] = value;
}
}
// ...
判断 depth 是否大于 1, 如果等于 1 则直接填入新数组,如果大于 1 则继续递归
// ...
if (depth > 1) {
// Recursively flatten arrays (susceptible to call stack limits).
baseFlatten(value, depth - 1, predicate, isStrict, result);
} else {
arrayPush(result, value);
}
// ...
JS 中的 Infinity
flattenDeep 中借助了 Infinity 来完成无限循环,这里就展开说说 Infinity 的一些特性和作用
Inifity 代表了JS 中最大的数字值,在 JS 中用数字类型可以存储的最大值 2^1024 表示,其本身却大于 Number.MAX_VALUE (用 1.79e+308 表示) 。
Infinity 和任何数字加减乘都会得到 Infinity,1/Inifnity 会得到 0。
Infinity 之间的运算,加乘结果是 Infinity,减除结果是 NaN
早期的 JS当中 Inifity 值是可以更改的,后来在 ES5 中更改为只读值。而 Infinity 还分为 Infinity 和 -Infinity,一个是最大值一个是最小值。Infinity 值的作用是其作为最大数字和不会因加上数字变化,可以用来比较大小,无限次数的循环
Number.isFinite 和 isFinite 都可以可以用来判断是否是 Infinity、-Infinity、NaN 这三个无限值,区别在于 Number.isFinite 不会强制转换类型,即输入其他不会判断为 NaN;isFinite 正好相反。
值得注意的是,ES2020 新特性 BigInt 类型因为没有位数限制的关系,因此不具有 Infinity 值,也不可以与其进行计算
通常可以用下面这几种办法来获取 Infinity 值
Number.POSITIVE_INFINITY
Infinity
1/0
2**1024
Math.pow(2, 1024)
总结
flattenDeep 利用 Infinity 无限递归,展平数组
Infinity 是 number 类型,因此 depth 参数不仅可以实现无限递归,还可以兼容传入固定的展平层数