重学ES6 函数的扩展(下)

本文深入探讨了函数式编程中尾调用的概念,解释了尾调用如何节省内存资源,避免栈溢出,并通过实例展示了尾递归的实现方式及优化技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

尾调用优化

什么是尾调用

尾调用(Tail Call)是函数式变成的重要概念,本身很简单,就是指函数的最后一步,调用另一个函数。

function f(x){
    return g(x)
}
// 函数最后一步调用了 g(x)
复制代码

以下几种都不属于尾调用

//1
function f(x){
    let y = g(x);
    return y;
}
//2
function f(x){
    return g(x) + 1
}
//3
function f(x){
    g(x);
}
复制代码

1 是函数g之后还有赋值操作

2 是 调用后还有操作

3 相当于

function f(x){
    g(x);
    return undefined;
}
复制代码

尾调用不一定出现在函数的尾部

function f(x) {
    if(x > 0){
        return m(x);
    }
    return n(x);
}
复制代码

m 和 n 都属于尾调用,都是 f 的最后一步

尾调用优化

尾调用,特殊就在于他的调用位置。

函数调用,会在内存形成一个 调用记录 ,也叫 调用帧,保存调用位置和内部变量等信息。

如果函数A内部调用函数B,那么在A的调用帧上方还会形成一个B的调用帧。等B运行结束,将结果返回A,B的调用帧才会消失。如果函数B内部还调用函数C,那么还有一个C的调用帧,以此类推,调用帧就形成一个“调用栈(call stack)”

尾调用是函数的最后一步操作,所以不需要保存外部函数的调用帧,因为调用位置和内部变量等都不会再用到了,直接用内层函数的调用帧取代外层函数即可。

function f(){
    let m = 1;
    let n = 2;
    return g(m+n);
}
f();

// 等同
function f() {
    return g(3)
}
f()

//等同于
g(3)
复制代码

分析:如果上边函数g不是尾调用的话,f就需要保存内部变量 m 和 n,g的调用位置信息等。但是调用 g 之后,函数 f 就结束了,就可以删除 f 的调用帧,只保留 g(3)的调用帧。这将大大节省内存

只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就不能进行“尾调用优化”

尾递归

函数调用自身称为递归,如果尾调用自身就称为尾递归。

递归非常消耗内存,要同时保存成百上千的调用帧,很容易栈溢出,但是对于尾递归,则永远不会发生 栈溢出 错误。

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

fac(5)
复制代码

这个阶乘函数的时间复杂度为 O(n)

改成尾递归

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

fac(5,1)
复制代码

斐波那契数列,也是一个递归的例子

function fibnacci(n){
    if(n <= 1) return 1;
    return fibnacci(n-1) + fibnacci(n-2);
}
fibnacci(10) //89
fibnacci(100) //堆栈溢出
复制代码

尾调用递归优化

function fibnacci(n,ac1,ac2){
    if(n <= 1) return 1;
    return fibnacci(n,ac2,ac1+ac2)
//都不会溢出
//fibnacci(10) 89
//fibnacci(100)
//fibnacci(1000)
//fibnacci(10000) infinity
复制代码

递归函数的改写

尾递归往往需要改写递归函数,确保最后一步只调用自身,做到这一点的方法就是把所有用到的内部变量改成函数的参数,不过也有一些缺点,就是看起来不是很直观。

不直观的问题也是可以解决的。

function tailFac(n, total){
    if(n === 1) retturn total;
    return tailFac(n - 1, n * total)
}
function fac(n){
    return tailFac(n, 1)
}
复制代码

函数式编程还有一个概念,叫做“柯里化”,意思是,将多参数转化为单参数形式。这里也可以使用柯里化

function currying(){
    return function (m){
        return fn.call(this, m, n)
    };
}

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

const factorial = currying(tailFactorial, 1);

factorial(5)
复制代码

使用ES6的函数默认值

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

factorial(5)
复制代码

转载于:https://juejin.im/post/5cc250386fb9a0322f7c90df

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值