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>
这种蹦床方法本质上是实现尾递归的辅助函数。这将所有嵌套的函数调用转换为顺序的函数调用,从而避免堆栈溢出问题。
蹦床函数只是将递归代码包装在循环中,并逐段调用该递归函数,直到没有剩余的递归调用。
结论
在本文中,我们讨论了蹦床函数的概念。我们从介绍蹦床函数开始,并通过各种代码示例将其与尾调用递归或优化进行了比较。希望您学到了很多并乐在其中。
数据结构
网络
关系型数据库管理系统 (RDBMS)
操作系统
Java
iOS
HTML
CSS
Android
Python
C语言编程
C++
C#
MongoDB
MySQL
Javascript
PHP