写点东西《JavaScript 中的递归》

写点东西《JavaScript 中的递归》

  • 您是否曾经发现自己需要在 JavaScript 中循环遍历一个复杂的多维对象,却不知道如何操作?
  • 那么,递归函数到底是什么?
  • 让我们回到我们的树对象。
  • 为什么使用递归
  • 更多精彩

写点东西《JavaScript 中的递归》_第1张图片

您是否曾经发现自己需要在 JavaScript 中循环遍历一个复杂的多维对象,却不知道如何操作?

如果是这样,您应该考虑使用搜索引擎作为查找解决方案的强大工具。但是,既然您已经在这里,如果您继续阅读,您就会找到解决此问题的优雅解决方案。

让我们以以下树为例开始。

const mySuperComplexTree = {
  name: "root",
  children: [
    {
      name: "child-1",
      children: [
        {
          name: "child-1-1",
          children: [
            {
              name: "child-1-1-1",
              children: []
            }
          ]
        }
      ]
    },
    {
      name: "child-2",
      children: []
    }
  ]
};

问题:您必须将每个节点名称(包括根节点)打印到控制台。

当我们第一次遇到这类问题时,我们倾向于被邓宁-克鲁格效应所误导,并认为可以用一个简单的 for 循环来解决。简短的回答是肯定的……但同样,那并不总是最好的方法。

让我们看看当我们遵循这种方法时会发生什么:

console.log(mySuperComplexTree.name);
for (const node of mySuperComplexTree.children) {
  console.log(node.name);
  for (const node2 of node.children) {
    console.log(node2.name);
    for (const node3 of node2.children) {
      console.log(node3.name);
    }
  }
}

// Output:
// root
// child-1
// child-1-1
// child-1-1-1
// child-2

GIF

正如您可能看到的,即使它起作用了,此解决方案也存在几个问题:

  1. 首先,它不容易阅读。没有人想在他们的代码中看到一堆嵌套循环,而且你是团队成员,对吧?
  2. 如果这棵树在运行时需要发生变化,会发生什么?当前的实现不是动态的,因此您必须修改代码并手动添加更多嵌套循环。这会使代码更难阅读且更难维护。

**
这就是递归派上用场的地方。**

那么,递归函数到底是什么?

GIF

写点东西《JavaScript 中的递归》_第2张图片

它是一个在执行期间调用自身的函数。此特性允许它用于解决可以分解为与整体问题相同的更小、更简单的子问题的问题。

一个简单的方法是使用递归来实现倒计时功能。让我们看看。

function countDownFrom(n) {
  if (n < 0) return;
  console.log(n);
  countDownFrom(n - 1);
}
countDownFrom(10);

在此示例中, countDownFrom 函数打印一个数字,然后使用您传递的数字(减一)调用自身(递归),重复此过程,直到达到基本情况(在本例中,当 n 小于 0 时)。

正如你所见,它基本上是一个循环,但更简单、更优雅。

让我们回到我们的树对象。

我们的初始问题是,我们需要打印树的所有节点名称,我们将使用递归来解决它,因为我们注意到我们的树具有一个常量结构,其中每个节点都有一个 name 属性(我们要打印的字符串)和一个 children 属性(一个节点数组)。

因此,我们的函数需要接收一个节点并打印其名称,然后由于所有子节点都是相似的,我们可以循环一次子节点并仅调用传递子节点的相同函数。这将接收一个母鹿,打印其名称并重复该过程。

function printNodeNames(tree) {
  console.log(tree.name);
  for (const node of tree.children) {
    printNodeNames(node);
  }
}

printNodeNames(mySuperComplexTree);

// Output:
// root
// child-1
// child-1-1
// child-1-1-1
// child-2

正如您所见, printNodeNames 函数解决了我们的初始问题。

它首先打印当前节点的名称(作为参数传递)。然后,它循环遍历当前节点的子节点数组,并针对每个子节点调用自身(再次递归),将子节点作为新参数传递。此过程一直持续到它打印树中的所有节点名称。这正是我们所说的我们需要的东西。

此函数演示了遍历树结构的常见模式:处理当前节点(在本例中,打印其名称),然后递归处理所有子节点。

这种技术被称为深度优先遍历,因为它在回溯之前尽可能深入树中。

为什么使用递归

如果您出于某种原因尚未被说服使用递归函数,我将为您提供四个递归函数如此有用的原因。

  1. 简单性:递归通常比其迭代对应项(for、while 等)更容易理解。它们可以将复杂的任务变成更简单的任务。
  2. 问题解决:有些问题本质上是递归的,例如树遍历、河内塔等。对于此类问题,使用递归函数更容易。
  3. 分而治之:递归函数允许您将较大的问题分解为较小、更易于管理的子问题。这是许多高效算法(如归并排序和快速排序)的基础。
  4. 更少的代码:递归函数可以减少代码量。好的,更短的代码并不一定意味着更好,但它可以使代码更具可读性。

总之,递归不仅是某些问题的优雅解决方案,而且是将任务分解为更小任务(例如遍历树或对列表进行排序)的强大且有用的工具。

它们的关键组件是基本情况(函数停止调用自身的情况)和递归情况(函数调用自身的部分)。

但是,与迭代解决方案相比,它们可能更难理解和调试,并且如果未仔细实现,还可能导致堆栈溢出等性能问题。因此,谨慎使用递归并确保始终可以访问基本情况非常重要。


更多精彩

点击这里~~

你可能感兴趣的:(写点东西,javascript,入门,javascript,开发语言,ecmascript)