使用递归来去除对象中的 null 字段

1,342 阅读3分钟

太长不看版:

一个函数,如果直接或者间接调用自己,那么它就是递归函数。其中,斐波那契数列就是一个用递归解决问题的常见例子,但其实递归有着更多的应用比如文件系统的目录结构,嵌套数据结构的校验和转换等

什么是递归?

我们顺便来学个单词: recursion。它是递归的意思,有点不直观,我们来看看另一个 curve 曲线,那么 re-curve 就是不断重复的曲线了。

The factorial of a positive integer n, denoted by n!, is the product of all positive integers less than or equal to n.

整数n的阶乘,记做 n!,是所有小于或者等于 n 的整数的结果。比如,要计算 5 的阶乘:

5! = 1 * 2 * 3 * 4 * 5
   = 2 * 3 * 4 * 5
   = 6 * 4 * 5
   = 24 * 5

如果我们想计算 6 的阶乘,只需要把上面的结果再乘以 6。7 的阶乘就是 7 * 6!如果我们把结果整理成一个表,会发现如下模式:

n !n - -
1 1 - -
2 2 * 1 = 2(1!) = 2
3 3 * 2 * 1 = 3(2!) = 6
4 4 * 3 * 2 * 1 = 4(3!) = 24
5 5 * 4 * 3 * 2 * 1 = 5(4!) = 120

可以看出在表格中的每一行都依赖之前的内容,除了第一行。第一行一般叫做递归的基类。这就意味着如果我们想要计算任何正整数 n 的阶乘,我们从计算 n - 1 开始,然后会计算 n - 2,直到到达基类 !1 = 1 为止。

代码

下面是递归函数的 Javascript 实现:

const factorial = n => {
  if (n <= 1) {
    return 1
  }
  return factorial(n - 1) * n
}

用三目来简写如下:

const factorial = n => n <= 1 ? 1 : factorial(n - 1) * n;

如果忘记了基类条件呢?

如果我们移走之前代码中的条件判断,只保留递归调用的话:

const factorial = n => factorial(n -1) * n

这样的调用会引发堆栈溢出,俗称爆栈,也叫内存泄漏。大多数时候,没有基类的递归调用不应该存在。但是在尾递归中有所不同。

递归很酷,但是能做点什么有用的吗?

终于要回归正题了。我们假设在带宽有限的情况下,数据传输很慢。所以需要优化数据接口,我们需要把一个对象中的任意嵌套深度的 null 值给过滤掉,这个时候,递归就派上用场了,直接上代码。

注释即正文:

const isObject = value => Object(value) === value

const removeNullValues = object =>
  /* `Object.entries()`方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环也枚举原型链中的属性)。
  以上来自MDN的介绍,通过 Object.entries 我们可以得到一个 [key, value] 排列的数组,方便接下来的 reduce 直接使用。
  */
  Object.entries(object).reduce((acc, [key, value]) => {
  // 当值为 null 时,过滤掉
  if (value === null) {
    return acc
  }
  if (isObject(value)) {
    // 如果value 是一个引用类型,也就是说 value 里面还有对象,我们就需要再次调用函数自身进一步解析里面的值,过滤 null
    const filteredValue = removeNullValues(value)
    // 也有可能这个对象是一个空对象,也是我们不想要的,剔除空对象操作,比如: dolor: {}
    if (Object.entries(filteredValue).length === 0) {
      return acc;
    }
    // 最后函数从这里结束并返回,函数removeNullValues 会得到正确的结果
    return { ...acc, [key]: filteredValue }
  }
  // 这一步操作是跟reduce用法相关,我们需要收集每次遍历后获取的有效属性, acc 是一个累计值
  return { ...acc, [key]: value }
}, {})

removeNullvalues({
  foo: 'foo',
  bar: null,
  baz: {
    qux: null,
    quux: 'quux',
    corge: {
      lorem: null,
      ipsum: null,
      dolor: {}
    }
  }
})

// [object Object] {
//   baz: [object Object] {
//     quux: "quux"
//   },
//   foo: "foo"
// }

本文完,感谢阅读!

pic