lodash 源码解析 -- flattenDeep

1,058 阅读3分钟

lodash 版本 4.17.20

前言

flattenDeep 是用于展平多维数组或内嵌数组的数组工具函数,lodash 实现这个简单的函数的时候,为了其复用代码而用了很多巧妙的思路,比如使用了 Infinity 的特性,使得 flattenDeepflattenunion 等函数都可以共用一份代码

为了代码的健壮性,flattenDeep 会判断属性 Symbol.isConcateSpreadable ,这个属性控制对象是否可以被合并,即定义调用 concat() 函数时的行为,因此 flattenDeep 也可以对类数组对象使用

思路分析

流程图

源码分析

1. flattenDeep

  1. 传入参数分析
  • array 目标数组,被展平的数组或类数组对象
  1. 源码分析 传入数组,并传入 Infinity 作为展平层数
function flattenDeep(array) {
  var length = array == null ? 0 : array.length;
  return length ? baseFlatten(array, INFINITY) : [];
}

2. baseFlatten

  1. 传入参数分析
  • array 目标数组,被展平的数组或类数组对象
  • depth 展平的层数,有多少层就会递归展平多少次
  • predicate 判断是否可行,默认 isFlattenable,判断是否可展平
  • isStrict 限制通过 predicate 函数的值,准备以后说到 union 函数时再详细分析
  • result 递归结果,可以在多次递归中传递形成最终结果
  1. 源码分析
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;
}

初始化 indexlength,处理 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 表示) 。

微信截图_20210319141705.png

Infinity 和任何数字加减乘都会得到 Infinity,1/Inifnity 会得到 0

微信截图_20210319141720.png

Infinity 之间的运算,加乘结果是 Infinity,减除结果是 NaN

微信截图_20210319141732.png

早期的 JS当中 Inifity 值是可以更改的,后来在 ES5 中更改为只读值。而 Infinity 还分为 Infinity-Infinity,一个是最大值一个是最小值。Infinity 值的作用是其作为最大数字和不会因加上数字变化,可以用来比较大小,无限次数的循环

Number.isFiniteisFinite 都可以可以用来判断是否是 Infinity-InfinityNaN 这三个无限值,区别在于 Number.isFinite 不会强制转换类型,即输入其他不会判断为 NaNisFinite 正好相反。

微信截图_20210319141743.png

值得注意的是,ES2020 新特性 BigInt 类型因为没有位数限制的关系,因此不具有 Infinity 值,也不可以与其进行计算

通常可以用下面这几种办法来获取 Infinity

Number.POSITIVE_INFINITY
Infinity
1/0
2**1024
Math.pow(2, 1024)

引用:What is Infinity in JavaScript?

总结

flattenDeep 利用 Infinity 无限递归,展平数组

Infinitynumber 类型,因此 depth 参数不仅可以实现无限递归,还可以兼容传入固定的展平层数