尾调用(Tail Call)

456 阅读2分钟

尾调用(Tail Call)是函数式编程的一个重要概念 一定是在最后一步执行

尾调优化

"尾调用优化"(Tail call optimization),即只保留内层函数的调用记录。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。这就是"尾调用优化"的意义

应用

  1. 尾递归 正常递归会有一系列的调用记录在内存中,尾递归则每调用一次就会产出一次;很容易发生"栈溢出"错误(stack overflow)

     function factorial(n) {
         if( n==1) return 1;
         
        return n*factorial(n-1)
     }
    

    上面的就可能出现stack oveflow,经过优化

      function factorial(n,total){
         if(n === 1) rerturn total;
         return factorial(n-1,n*total)
      }
    

    由此可见,"尾调用优化"对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6也是如此,第一次明确规定,所有 ECMAScript 的实现,都必须部署"尾调用优化"。这就是说,在 ES6 中,只要使用尾递归,就不会发生栈溢出,相对节省内存。

  2. 递归改写 方法:就是把所有用到的内部变量改写成函数的参数。比如上面的例子,阶乘函数 factorial 需要用到一个中间变量 total ,那就把这个中间变量改写成函数的参数。

    function factorial(n){
         tail(n,1)
    }
    function tail(n,total){
        if(n==1) return total;
        return tail(n-1,n*total)
    }
    
    

    curry化 也可以这样改造的 什么是curry化呢,意思是将多参数的函数转换成单参数的形式。这里也可以使用柯里化。

    function curry(fn,n) {
      return function(m) {
         fn.call(this,m,n);
      }
    } 
    function tail(n,total){
        if(n==1) return total;
        return tail(n-1,n*total)
    }
    let exec = curry(tail,1)
    es6 模式更简单
    
    function factorial(n,total=1) {
      ……
    }