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>  

这种蹦床方法本质上是实现尾递归的辅助函数。这将所有嵌套的函数调用转换为顺序的函数调用,从而避免堆栈溢出问题。

蹦床函数只是将递归代码包装在循环中,并逐段调用该递归函数,直到没有剩余的递归调用。

结论

在本文中,我们讨论了蹦床函数的概念。我们从介绍蹦床函数开始,并通过各种代码示例将其与尾调用递归或优化进行了比较。希望您学到了很多并乐在其中。

更新于:2023年1月19日

357 次浏览

启动您的职业生涯

完成课程获得认证

开始
广告