javascript递归
在使用JavaScript编程时,您可能会遇到对递归函数的引用。 您甚至可能尝试自己构造(或解构)一些。 但是您可能还没有看到很多有效的递归示例。 实际上,除了这种方法的特殊性质之外,您可能没有考虑过递归的使用时间和地点,或者如果不小心使用递归可能有多危险。
递归有什么用?
递归是一种迭代操作的技术,方法是使函数重复调用自身直到获得结果。 大多数循环可以以递归方式重写,在某些功能语言中,这种循环方法是默认的。
但是,尽管JavaScript的功能编码风格确实支持递归函数,但我们需要意识到,大多数JavaScript编译器当前尚未进行优化以安全地支持它们。
当您需要在循环中使用不同的参数重复调用同一函数时,最好应用递归。 尽管可以在许多情况下使用它,但它对于解决涉及迭代分支的问题(例如分形数学,排序或遍历复杂或非线性数据结构的节点)最为有效。
函数式编程语言偏爱递归的原因之一是,它允许构建不需要使用局部变量设置和维护状态的代码。 递归函数自然也很容易测试,因为它们很容易以纯粹的方式编写,对于任何给定的输入都具有特定且一致的返回值,并且对外部变量状态没有副作用。
循环播放
可以应用递归的函数的经典示例是阶乘。 这个函数返回一个数字一次又一次地乘以每个前面的整数的值,一直下降到一个。
例如,三个阶乘为:
3 × 2 × 1 = 6
阶乘六是:
6 × 5 × 4 × 3 × 2 × 1 = 720
您可以看到这些结果变得有多快。 您还可以看到我们不断重复相同的行为。 我们将一个乘法运算的结果乘以比第二个值小一的结果。 然后,我们一次又一次地做到这一点,直到达到目标为止。
使用for循环,创建一个将迭代执行此操作直到返回正确结果的函数并不困难:
var factor = function(number) {
var result = 1;
var count;
for (count = number; count > 1; count--) {
result *= count;
}
return result;
};
console.log(factor(6));
// 720
这行得通,但是从功能编程的角度来看并不是很优雅。 为了支持for循环,我们必须使用几个局部变量来维护和跟踪状态,然后返回结果。 如果我们可以放弃for循环并采用功能更强大JavaScript方法,那会不会更干净?
递归
我们知道JavaScript将使我们能够编写以函数为参数的函数 。 那么,如果我们想使用实际的函数,我们将在运行它的上下文中编写并执行它。
那有可能吗? 你打赌它是! 例如,以一个简单的while
循环为例:
var counter = 10;
while(counter > 0) {
console.log(counter--);
}
完成此操作后, counter
的值已更改,但是当我们慢慢从中吸取其状态时,该循环已完成了打印其持有的每个值的工作。
相同循环的递归版本可能更像这样:
var countdown = function(value) {
if (value > 0) {
console.log(value);
return countdown(value - 1);
} else {
return value;
}
};
countdown(10);
您是否看到我们如何在countdown
功能的定义内调用countdown
功能? JavaScript像老板一样处理这些事情,并且可以满足您的期望。 每次执行countdown
,JavaScript都会跟踪它的调用位置,然后在该函数调用堆栈中进行反向操作,直到完成为止。 我们的函数还避免了修改任何变量的状态,但是仍然利用了传入值来控制递归。
回到我们的阶乘情况,我们可以像这样重写我们以前的函数以使用递归:
var factorial = function(number) {
if (number <= 0) { // terminal case
return 1;
} else { // block to execute
return (number * factorial(number - 1));
}
};
console.log(factorial(6));
// 720
这样编写代码使我们能够以无状态的方式描述整个过程,而没有副作用。 同样值得注意的是,在进行任何计算之前,我们首先测试传递给函数的参数值。 我们希望将要调用的所有函数在到达终端盒时快速干净地退出。 对于以这种方式计算的阶乘,当传入的数字为零或负数时会出现终端情况(如果需要,我们也可以测试负数并返回不同的消息)。
尾叫优化
当代JavaScript实现的一个问题是,它们没有一种标准的方法来防止递归函数无限期地堆积在自己身上,并吞噬内存,直到它们超过引擎的能力为止。 JavaScript递归函数需要跟踪每次调用它们的位置,因此它们可以在正确的位置恢复。
![](https://i-blog.csdnimg.cn/blog_migrate/6e1afbd244de721145d6447776fe4601.png)
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
在许多功能语言中,例如Haskell和Scheme,使用称为尾部调用优化的技术对其进行管理。 通过尾部调用优化,递归函数中的每个连续周期将立即发生,而不是堆积在内存中。
从理论上讲,尾部调用优化是ECMAScript 6标准的一部分,ECMAScript 6是当前JavaScript的下一个版本,但是大多数平台尚未完全实现它。
蹦床功能
有必要时可以强制JavaScript以安全的方式执行递归函数的方法。 例如,可以构造一个定制的蹦床函数来迭代地管理递归执行,一次只在堆栈上保留一个操作。 以这种方式使用的蹦床功能可以利用JavaScript将功能绑定到特定上下文的能力,从而使递归功能反其自身,一次生成一个结果,直到循环完成。 这将避免创建等待执行的深层操作堆栈。
在实践中,利用蹦床功能通常会减慢性能,从而增加安全性。 此外,通过以递归方式编写函数而获得的许多优雅和可读性都丢失了使这种方法在JavaScript中起作用所需的代码卷积。
如果您感到好奇,我鼓励您阅读有关此概念的更多信息,并在下面的讨论中分享您的想法。 您可以从StackOverflow上的一小段线程开始,然后探究Don Taylor和Mark McDonnell撰写的一些文章,这些文章更深入地介绍了JavaScript中蹦床的兴衰。
我们还没有
递归是一项功能强大的技术,值得了解。 在许多情况下,递归是解决复杂问题的最直接方法。 但是,在我们需要通过尾部调用优化在所有需要实现的地方实施ECMAScript 6之前,我们需要非常小心如何以及在何处应用递归。
翻译自: https://www.sitepoint.com/recursion-functional-javascript/
javascript递归