ES6 蹦床函数
在本教程中,我们将主要关注 ES6(ECMAScript 6)中首次出现的蹦床函数。我们将从讨论蹦床函数开始,探讨使用蹦床函数的必要性,以及使用蹦床函数的优缺点。
现在,让我们看看蹦床函数。
什么是蹦床函数?
蹦床只是一种优化递归并防止在不提供尾调用优化(例如 JavaScript ES5)的语言中出现堆栈溢出错误的机制。为了克服尾调用优化的这个问题,ES6 版本中引入了蹦床。
蹦床函数本质上是一个围绕我们的递归函数的循环。它逐个调用递归代码,直到不再生成递归调用。如果蹦床函数如此有益,那么递归到底有什么问题呢?让我们在本文的下一节中看看。
使用递归的问题
首先,让我们看看什么是递归。递归是一个过程,我们使用相同的函数来计算任何结果值,但通过由栈数据结构控制的许多不同的函数调用来执行。递归简化了代码,对于较大的数量,逐段解决问题。
普通递归的问题是,每次递归调用都会向调用栈增加一个栈项,这可以看作是一个调用金字塔。这是一个递归调用阶乘函数的示例:
(factorial 4) (* 4 (factorial 3)) (*4 (*3 (factorial 2))) (*4 (*3 (*2(factorial 1)))) (*4 (*3 (*2(*1(factorial 0))))) (*4 (*3 (*2(*1(1))))) (*4 (*3 (*2(1)))) (*4 (*3 (2))) (*4 (6)) 24
这是一个堆栈可视化,其中每个垂直短划线代表一个堆栈帧:
—|— —|— —|— —|— —|— —|— —|— —|— —|—
问题是堆栈的大小是有限的,堆积这些堆栈帧会导致堆栈溢出。根据其大小,更大的阶乘计算可能会导致堆栈溢出。因此,在 C#、JavaScript 等语言中,常规递归可能被认为是有风险的。
可以使用尾递归解决堆栈溢出的问题。现在,让我们谈谈尾递归的概念。
为什么使用蹦床函数?
在学习蹦床函数之前,让我们详细了解递归、尾递归的概念以及代码示例。
尾递归到底是什么?从本质上讲,它进行跳转并重用先前递归调用的上下文,因此没有额外的内存成本,而不是将递归函数作为返回值调用。
这意味着,只要我们在递归函数中使用适当的尾递归,内存使用量就会保持不变,而不是随着每次递归调用的内存消耗不断增加。例如,正确实现的阶乘函数的尾递归实现的内存使用情况可能如下所示。
为了更好地理解,让我们使用递归和尾递归来查找数字的阶乘。
使用递归
示例
<html> <body style = "text-align: center; font-size: 20px;"> <h2>Using Recursion</h2> Enter a number: <input id = "digit"> <br><br> <button onclick = "factorial()"> Factorial </button> <p id = "result"></p> <script> function fact(num) { if (num == 0) { return 1; } else { return num * fact( num - 1 ); } } function factorial() { var num = document.getElementById("digit").value; var fx = fact(num); document.getElementById("result").innerHTML="The factorial of the number " + num + " is: " + fx ; } </script> </body> </html>
如上所示,每个阶乘调用首先执行。只有这样,才能将整个数字相乘。为了将其转换为尾递归,我们正在修改函数以将结果作为第二个参数。
使用尾递归
示例
<html> <body style = "text-align: center; font-size: 20px;"> <h2>Using tail recursion</h1> Enter a number: <input id = "digit"> <br><br> <button onclick = "factorial()"> Factorial </button> <p id = "result"></p> <script> function fact(num,result=1) { if(num === 1) { return result; } else { return fact(num - 1, result * num); } return result; } function factorial() { var num = document.getElementById("digit").value; var fx = fact(num); document.getElementById("result").innerHTML="Thefactorial of the number " + num + " is: " + fx ; } </script> </body> </html>
这种类型的执行效率更高,因为它需要更少的堆栈操作和对象。
使用蹦床函数
示例
<html> <body style = "text-align: center; font-size: 20px;"> <h1> Using Trampoline Function </h1> Enter a number: <input id = "digit"> <br><br> <button onclick = "factorial()"> Factorial </button> <p id = "result"></p> <script> const tram_fn = (fx) => (...arguments) => { let result = fx(...arguments); while (typeof result === 'function') { result = result(); } return result; }; const sumCalculation = (num, sum = 1) => { return num === 1 ? sum : () => sumCalculation(num - 1, sum * num); }; function factorial(){ var num1 = document.getElementById("digit").value; const sumCalculate = tram_fn(sumCalculation); document.getElementById("result").innerHTML="The factorial of the number " + num1 + " is: " + sumCalculate(num1); } </script> </body> </html>
这种蹦床方法本质上是实现尾递归的辅助函数。这将所有嵌套的函数调用转换为顺序的函数调用,从而避免堆栈溢出问题。
蹦床函数只是将递归代码包装在循环中,并逐段调用该递归函数,直到没有剩余的递归调用。
结论
在本文中,我们讨论了蹦床函数的概念。我们从介绍蹦床函数开始,并通过各种代码示例将其与尾调用递归或优化进行了比较。希望您学到了很多并乐在其中。