递归的工作方式类似于 JavaScript 中的循环。只要条件为真,循环允许您多次执行一组代码。
在本文中,我将解释什么是递归以及它在 JavaScript 中的工作原理。
在循环中,当条件变为假时,执行停止。如果执行条件永远保持为真,您将获得一个无限循环,这可能会使您的应用程序崩溃。
递归也是如此——只要递归的条件仍然成立,递归就会一直发生,直到某个条件停止它,否则,你会得到一个无限递归。
那么,现在让我们潜入...
目录
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
,它是从第一个函数调用的。
在第二个, 执行期间printHello
,console.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
并且函数结束。然后调用堆栈上的所有内容在它们完成执行后开始弹出。
包起来
递归并不完全是循环的替代品。但在某些情况下,递归可以用更少的代码行更有效且更易于阅读。
在本文中,您了解了递归的概念,即只要满足一般情况,函数就会调用自身,直到基本情况停止为止。您还看到了它与循环的比较,以及它如何与调用堆栈一起工作。