递归如何工作?用示例简化 JavaScript

递归的工作方式类似于 JavaScript 中的循环。只要条件为真,循环允许您多次执行一组代码。

在本文中,我将解释什么是递归以及它在 JavaScript 中的工作原理。

在循环中,当条件变为假时,执行停止。如果执行条件永远保持为真,您将获得一个无限循环,这可能会使您的应用程序崩溃。

递归也是如此——只要递归的条件仍然成立,递归就会一直发生,直到某个条件停止它,否则,你会得到一个无限递归

那么,现在让我们潜入...

目录

1.什么是递归?

2.什么是调用堆栈?

3.递归和调用栈 

4.递归中的一般情况和基本情况

包起来


1.什么是递归?

递归是一个函数调用自身并不断调用自身直到被告知停止的概念。

让我们看一个例子:

function printHello() {
  console.log("hello")
}

printHello()

在这里,我们声明了一个printHello将“hello”记录到控制台的函数。然后,我们在定义之后调用函数。

在递归的情况下,我们也可以printHello从函数内部调用函数,printHello如下所示:

function printHello() {
  console.log("hello")

  printHello()
}

printHello()

// hello - first function call
// hello - second function call
// hello - third function call
// and it goes on infinitely

这是递归。因此,当 JavaScript 执行时printHello(),“hello”会打印到控制台,然后printHello()再次调用。以下是递归的发生方式:

  • printHello()第一次执行,“hello”打印到控制台,并在函数printHello()中再次调用
  • printHello()执行第二次,console.log("hello")再次运行,并printHello()再次调用
  • printHello()第三次执行,console.log("hello")再次运行,并printHello()再次调用
  • 它会一直持续下去,直到调用堆栈达到最大值并且应用程序崩溃

 

 

 让我们了解这个错误的含义。

2.什么是调用堆栈?

调用堆栈是 JavaScript 用来跟踪当前正在执行的函数的一种机制。

调用函数时,会将其添加到调用堆栈中。例如,上面的这个函数:

function printHello() {
  console.log("hello")
}

printHello()

 当这个函数被执行时,它被添加到调用栈中:

// printHello()
// ----
// call stack

 执行后(当所有代码运行时,或return遇到语句时),函数从堆栈中弹出:

// ----
// call stack

printHello例如,如果该函数调用另一个函数,如下所示: 

function printHi() {
  console.log("hi")
}

function printHello() {
  console.log("hello")

  printHi()
}

printHello()

 在这种情况下,调用堆栈将如下所示printHello

// printHello()
// ----
// call stack

 运行该console.log("hello")行后,下一行是printHi(),并且此调用被添加到堆栈顶部:

// printHi()
// printHello()
// ----
// call stack

printHi()调用完成执行后,从堆栈中弹出: 

// printHello()
// ----
// call stack

执行完成后printHello(),它也会从堆栈中弹出: 

// ----
// call stack

 那么递归如何与调用堆栈一起工作呢?

3.递归和调用栈 

 回到我们上面的递归代码:

function printHello() {
  console.log("hello")

  printHello()
}

printHello()

 这里发生的是,当printHello()被执行时,它被添加到调用堆栈中:

// printHello()
// ----
// call stack

 被console.log("hello")执行,然后printHello()再次执行,并被添加到调用堆栈的顶部:

// printHello()
// printHello() -- "hello"
// ----
// call stack

 

现在,我们当前在调用堆栈上有两个函数:第一个函数printHello和第二个函数printHello,它是从第一个函数调用的。

在第二个, 执行期间printHelloconsole.log("hello")被执行,并printHello再次被调用。现在堆栈看起来像这样:

// printHello()
// printHello() -- "hello"
// printHello() -- "hello"
// ----
// call stack

 事实上,我们没有任何停止递归的条件,所以printHello继续调用自身并填充堆栈:

// ...
// printHello() -- "hello"
// printHello() -- "hello"
// printHello() -- "hello"
// printHello() -- "hello"
// printHello() -- "hello"
// printHello() -- "hello"
// ----
// call stack

 然后,我们得到调用堆栈大小错误:

 

 为了避免这种使调用堆栈最大化的无限递归,我们需要一个停止递归的条件。

4.递归中的一般情况和基本情况

递归中的一般情况(也称为递归情况)是导致函数继续递归(调用自身)的情况。

递归中的基本情况是递归函数的停止点。这是您指定停止递归的条件(就像停止循环一样)。

这是一个例子:

let counter = 0

function printHello() {
  console.log("hello")
  counter++
  console.log(counter)

  if (counter > 3) {
    return;
  }

  printHello()
}

printHello()

 

在这里,我们的一般情况没有明确说明,而是隐含说明:如果计数器变量不大于 3,则函数应继续调用自身

虽然明确说明的基本情况是,如果计数器变量大于 3,则函数应该结束执行。这种情况将导致调用堆栈上的所有递归函数都被弹出,因为递归已经结束。

这是第一次调用调用堆栈时的样子printHello()

// printHello()
// ----
// call stack

 

然后记录“hello”,counter变量增加 1(使其变为1),并且counter变量也被记录。检查基本情况。"counter不大于3 ",所以还不满足条件。

函数的下一行在printHello()调用堆栈中:

// printHello()
// printHello() -- "hello" -- 1
// ----
// call stack

再次记录“hello”,counter变量增加并记录。基本情况不满足,因为“counter仍然不大于3 ”。然后,printHello()调用第二个函数中的 ,调用堆栈如下所示: 

// printHello()
// printHello() -- "hello" -- 2
// printHello() -- "hello" -- 1
// ----
// call stack

 发生相同的循环,并printHello()再次调用:

// printHello()
// printHello() -- "hello" -- 3
// printHello() -- "hello" -- 2
// printHello() -- "hello" -- 1
// ----
// call stack

 

在 "hello" 被记录到控制台后,counter增加 1 (使其成为4)。“4 大于 3”符合我们的基本情况,因此return执行该语句。

我们返回什么并不重要,但会return停止函数的执行。这意味着printHello()我们调用堆栈中的第四个将无法printHello()再次调用,因为未到达该行。

接下来发生的是第四个printHello()在完成执行时从调用堆栈中弹出:

// printHello() -- "hello" -- 3
// printHello() -- "hello" -- 2
// printHello() -- "hello" -- 1
// ----
// call stack

 对于第三个printHello(),在它调用自身的行之后,函数中没有任何内容要执行。所以这意味着第三个printHello()也完成了它的执行,并且将被弹出调用堆栈:

// printHello() -- "hello" -- 2
// printHello() -- "hello" -- 1
// ----
// call stack

 second 和 first 相同printHello(),从而使调用堆栈为空:

// ----
// call stack

 

因此,您会看到我们如何通过提供基本案例来避免无限递归。递归函数应该有至少一个基本情况(你可以有尽可能多的),以确保递归不会永远运行。

您可以通过多种方式编写基本案例。这是一般情况是显式的,而基本情况是隐式的:

let counter = 0

function printHello() {
  console.log("hello")
  counter++
  console.log(counter)

  if (counter < 4) {
      printHello()
  }

  return;
}

printHello()

在这里,我们有一个告诉函数继续递归的一般情况。这里的情况是counter IS LESS than 4。因此,如果遇到这种情况,递归就会继续发生。

但是不像前一个例子那样明确的基本情况是如果 counter 不长于 4,则继续下一行。这将执行return并且函数结束。然后调用堆栈上的所有内容在它们完成执行后开始弹出。

包起来

递归并不完全是循环的替代品。但在某些情况下,递归可以用更少的代码行更有效且更易于阅读。

在本文中,您了解了递归的概念,即只要满足一般情况,函数就会调用自身,直到基本情况停止为止。您还看到了它与循环的比较,以及它如何与调用堆栈一起工作。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值