javascript尾递归优化

article/2025/10/4 17:41:51

JS中的递归

我们来看一个阶乘的代码

function foo( n ){if(n <= 1){return 1;}return n * foo( n - 1 );
}foo(5);  // 120

下面分析一下,代码运行过程中,执行上下文栈是怎么变化的

  1. 这个代码是在全局作用域中执行的,所以在foo函数得到执行之前,上下文栈中就已经被放入了一个全局上下文。之后执行一个函数,生成一个新的执行上下文时,JS引擎都会将新的上下文push到该栈中。如果函数执行完成,JS引擎会将对应的上下文上下文栈中弹出

  2. 一开始执行foo函数的时候,JS引擎会创建foo的执行上下文,将该执行上下文push进上下文栈。然后开始执行foo中的代码。

在这里插入图片描述

现在上下文栈中已经有了两个执行上下文了

  1. 在执行到foo中代码快结束时,return表达式中,又调用了foo函数。所以又会创建一个新的执行上下文。并且JS引擎会把这新的执行上下文push到上下文栈中。

在这里插入图片描述

现在上下文栈中已经有了三个执行上下文了

  1. 开始重复第3步的执行。一直到n<=1,才不会有新的执行上下文产生。

此刻上下文栈中,已经有了6个上下文了(包含了全局上下文)

在这里插入图片描述

设想一下

  1. 如果刚开始调用的时候,传入n的初始值为100,到n<=1时,上下文栈中会有几个上下文。101个。
  2. 如果初始值为1000呢?到n<=1时,会有1001个执行上下文
  3. 也就是说,传入的初始值越大,执行上下文栈中,就会有越多的执行上下文🥲
  4. 对于上下栈,它的空间是有限的,一旦存放的上下文占用内存产出了它的最大内存,就会出现栈溢出。RangeError: Maximum call stack size exceeded
  5. 而在chrome中,不仅会对栈的空间有限制,还会对函数的递归次数有限制

递归优化

我们来看一个样例代码

function outer() {return inner();
}outer();

分析一下,这里的上下文栈是怎么变化的

  1. 调用outer函数的时候,第二个栈帧被推到了栈上。

第一个栈帧是全局上下文

把上下文栈中的一个上下文称作一个栈帧

在这里插入图片描述

  1. 执行到了return语句,必须要计算inner调用结果,才能返回值
  2. 调用inner函数,第三个栈帧被推入到栈上。

在这里插入图片描述

  1. 执行inner函数,将返回值传回到outer函数。inner执行完毕。第三个栈帧被弹出栈

在这里插入图片描述

  1. outer函数再返回值。outer函数执行完毕,第二个栈帧被弹出栈

在这里插入图片描述

等等,情况不是一样的么?优化在哪里

  1. 在执行到outer中的return语句的时候,要先计算inner函数的值。这时候JS引擎发现,把第二个栈帧弹出去也没有关系。因为到时候,直接拿inner的返回值返回出去就好了,第二个栈帧就没有必要保存了。参考视频讲解:进入学习
  2. 将第二个栈帧弹出

这个时候,栈中只有一个栈帧了–全局上下文

  1. 执行到inner函数,inner函数的上下文被push到栈中

在这里插入图片描述

这个时候,栈中有两个栈帧了

  1. 开始执行inner函数,计算返回值后,inner函数执行完毕。inner的上下文栈帧被弹出栈。

在这里插入图片描述

栈中又只剩一个栈帧了–全局上下文

综上,我们可以看出:如果没有优化,没多调用一次嵌套函数,就会多增加一个栈帧;有了优化之后,无论调用多少次嵌套,栈中只会有两个栈帧。这就是ES6尾调用优化的关键😄

递归优化的条件

  1. 代码在严格模式下执行
  2. 外部函数的返回值,是对尾调用函数的调用
  3. 尾调用函数返回后,不需要执行额外的逻辑
  4. 尾调用函数不是外部函数作用域中自由变量的闭包

下面是《高程》里面的示例,帮助大家理解

// 无优化: 尾调用没有返回
function outer(){inner();
}// 无优化: 尾调用没有直接返回
function outer(){let innerResult = inner();return innerResult;
}//无优化: 尾调用返回值后,必须要转型为字符串
function outer(){return inner().toString(); 
}// 无优化: 尾调用是一个闭包
function outer(){let foo = 'bar';function inner(){ return foo; }return inner();
}

其实我觉得上面的倒数第二个,它是完全可以尾调用优化的。因为这个计算是不需要外部函数的上下文里面内容支持的。可能是这样的计算必须要在外部函数的上下文中完成吧,咱也不懂。记一下吧。

有哪位同仁能够帮我解答一下我这个问题吗😁

实操一个优化代码

下面是一个普通的求斐波那契数列的函数

function fib( n ){if( n < 2){return n;}return fib(n - 1) + fib(n - 2)
}

这是一个非常简单的斐波那契数列的函数,可以看到它不符合尾递归的条件。因为返回值的调用函数参与了额外计算

我们来优化一下

function fib( n ){return fibImpl(0, 1, n);
}function fibImpl(a, b, n){if(n === 0){return a;}return fibImpl(b, a+b, n-1);
}

看,这样是不是就符合尾递归调用函数了

简单讲解一下上面的代码

  1. 把原先的一个函数拆成了两个
  2. 第一个函数接受一个参数。这个参数表示求第几位的斐波那契数。
  3. 第二个参数接收三个参数。前两个参数表示正在计算的两个位置的数字,第三个参数表示还要计算多少次

斐波那契数规律,就是从第三位开始,每一位的数字都是前两位数字的和

那上面的计算的阶乘代码怎么优化呢?

function foo( n ){if(n <= 1){return 1;}return n * foo( n - 1 );
}foo(5);  // 120

这个是上面计算阶乘的代码,我们可以用同样的思路,来对其做尾递归函数优化

function foo( n ){return inner(n, n - 1)
}function inner(sum, n){if(n <= 1){return sum;  }return inner(sum * n , n -1);
}foo(5);

是不是超简单😁

最新版的浏览器已经支持尾递归

可以在计算斐波那契数列的时候,比较尾递归和非尾递归的时间。相信你会和我一样,会不由自主的感叹

总结

  1. JS中的递归函数调用的时候,上下文栈是怎么变化的
  2. 什么是递归优化
  3. 递归优化的条件是什么
  4. 手动优化一个递归代码

http://chatgpt.dhexx.cn/article/h4T7jK9O.shtml

相关文章

Scala尾递归

一、首先来简单介绍一下递归和尾递归 1.递归&#xff1a; 简单来说就是在函数内部调用函数本身来完成函数体。对于返回值的要求并不很严格。 递归的缺点&#xff1a;递归效率比较低&#xff0c;调用次数过多还会出现栈溢出的问题。 2.尾递归&#xff1a; 尾递归的核心思想&…

java 递归 尾递归_递归和尾递归

C允许一个函数调用其本身&#xff0c;这种调用过程被称作递归(recursion)。 最简单的递归形式是把递归调用语句放在函数结尾即恰在return语句之前。这种形式被称作尾递归或者结尾递归&#xff0c;因为递归调用出现在函数尾部。由于为递归的作用相当于一条循环语句&#xff0c;所…

递归算法和尾递归

一、递归算法 1、递归算法思想 递归算法是在程序中不断反复调用&#xff08;间接或直接地调用&#xff09;自身&#xff0c;来达到解决问题的算法。 递归是一个方法在其方法体内调用自身方法的调用方式。递归调用分为两种情况&#xff1a; 直接递归&#xff0c;即在方法中调用…

尾递归(Tail Recursion)

向上递归(Pass-Up Recursion) 先来看一下传统的递归 def sum(lst):"""把lst所有元素求和"""if len(lst) 0:return 0return lst[0] sum(lst[1:])加入sum([1,2,3]),则是先求sum([2,3])&#xff1b;而要求sum([2,3]),则要求sum([3]), 求sum([3]…

尾递归

什么是尾递归 什么是尾递归呢?(tail recursion), 顾名思议&#xff0c;就是一种“不一样的”递归&#xff0c;说到它的不一样&#xff0c;就得先说说一般的递归。对于一般的递归&#xff0c;比如下面的求阶乘&#xff0c;教科书上会告诉我们&#xff0c;如果这个函数调用的深…

【算法】递归:递归优化之尾递归

【算法】递归&#xff1a;递归优化之尾递归 引言&#xff1a;在以往我发过一篇过于通过分析法去理解递归求解递归的博客文章&#xff0c;那篇文章主要介绍了如何去求解递归问题。而在这篇文章中&#xff0c;我会介绍一下如何去优化递归&#xff0c;顺带还会去分析一下递归算法的…

DDD微服务架构设计第四课 DDD指导微服拆分和落地实现

07 在线订餐场景中是如何开事件风暴会议的&#xff1f; 微服务设计最核心的难题是微服务的拆分&#xff0c;不合理的微服务拆分不仅不能提高研发效率&#xff0c;反倒还使得研发效率更低&#xff0c;因此要讲究“小而专”的设计。“小而专”的设计意味着微服务的设计不是简单拆…

微服架构的论述?

搭建微服架构 一、什么是微服架构 简单的说就是将一个整体的应用按照一定的规则拆分成一个个独立的应用&#xff0c;这些独立的应用后面又组合成了一个整体的应用。 比如说一个博客系统&#xff0c;我可能包含了发表文章&#xff0c;用户登录&#xff0c;用户评论等功能&…

微服务架构是什么?

微服务架构是什么&#xff1f; 一文详解微服务架构最初的需求随着业务发展……是时候做出改变了没有银弹监控 - 发现故障的征兆定位问题 - 链路跟踪分析问题 - 日志分析网关 - 权限控制&#xff0c;服务治理服务注册于发现 - 动态扩容熔断、服务降级、限流熔断服务降级限流 测试…

SpringCloud微服之Nacos的学习

1:使用前提 第一步:解压启动Nocos SpringCloudAlibaba 推出了一个名为 Nacos 的注册中心&#xff0c;在国外也有大量的使用。 startup.cmd -m standalone 访问http://localhost:8848/nacos/ 第二步:服务注册 工程目录 在父工程中添加依赖 <dependency><groupI…

python注册nacos微服并使用gateway网关

业务需求&#xff1a;使用python flask框架和java spring boot框架共同注册到nacos中&#xff0c;在由spring cloud gateway分配路由。 flaskDome: from flask import Flaskapp Flask(__name__)app.route(/python) def test():return "这是python flask框架接口&#xf…

大开眼界,Jenkins结合SpringCloud+K8S,打通微服一条龙技术讲解

Jenkins 是目前最常用的持续集成工具&#xff0c;拥有近50%的市场份额&#xff0c;他还是很多技术团队的第一个使用的自动化工具。由此可见他的重要性&#xff01; 这份Jenkins宝典从入门介绍到结合DockerSpringCloudKubernetes&#xff0c;打通一条龙技术讲解&#xff0c;简直…

基于Ant DesignPro Vue + SpringBoot 前后端分离 - 后端微服化 + 接口网关 + Nacos

基于Ant DesignPro Vue SpringBoot 前后端分离 - 后端微服化 接口网关 Nacos 通过Ant DesignPro Vue SpringBoot 搭建的后台管理系统后&#xff0c;实现了前后端分离&#xff0c;并实现了登录认证&#xff0c;认证成功后返回该用户相应权限范围内可见的菜单。 后端采用Spri…

微服务消费端通过feign调用微服异常问题

在项目开发中,我们的调用方通过Feign调用微服时,如果微服出现业务异常(例如空指针,或抛出自定义的异常)和非业务异常(参数不合法4xx异常)都会进入到调用方的全局异常拦截器,抛出的code全部转换成了500,这样不友好 实际上只有业务异常feign才会转换成500错误且转成FeignExcepti…

大开眼界!Jenkins结合SpringCloud+K8S,打通微服一条龙技术讲解

Jenkins 是目前最常用的持续集成工具&#xff0c;拥有近50%的市场份额&#xff0c;他还是很多技术团队的第一个使用的自动化工具。由此可见他的重要性&#xff01; 这份Jenkins宝典从入门介绍到结合DockerSpringCloudKubernetes&#xff0c;打通一条龙技术讲解&#xff0c;简直…

从0搭建公司SpringCloud Alibaba 微服務鉴权中心服务(详细教程)

大家好&#xff0c;我是宝哥&#xff01; 鉴权中心服务 认识JWT json web token 是一个开放的标准 &#xff0c;它定义了一个种紧凑的&#xff0c;自包含的方式&#xff0c;用于作为json对象在各方之间安全的传输信息 服务器鉴权完成之后 会生成 json 对象 发送给客户端&#x…

腾讯云开源业界微服管理框架 Femas

导读 企业数字化向云原生演进过程面临诸多痛点&#xff0c;微服务框架不统一、协议多样化、语言异构&#xff0c;纷繁复杂的微服务技术栈&#xff0c;基础组件之间像一座座孤岛&#xff0c;各个基础组件的控制面不能互联&#xff0c;让用户的体验非常割裂&#xff0c;各种历史包…

基于Ant DesignPro Vue + SpringBoot 前后端分离 - 后端微服化 + 接口网关 + Nacos + Sentinel

基于Ant DesignPro Vue SpringBoot 前后端分离 - 后端微服化 接口网关 Nacos Sentinel 通过Ant DesignPro Vue SpringBoot 搭建的后台管理系统后&#xff0c;实现了前后端分离&#xff0c;并实现了登录认证&#xff0c;认证成功后返回该用户相应权限范围内可见的菜单。 后…

分布式/微服务必配APM系统,SkyWalking让你不迷路

前言 如今分布式、微服务盛行&#xff0c;面对拆分服务比较多的系统&#xff0c;如果线上出现异常&#xff0c;需要快速定位到异常服务节点&#xff0c;假如还用传统的方式排查肯定效率是极低的&#xff0c;因为服务之间的各种通信会让定位更加繁琐&#xff1b;所以就急需一个…

大开眼界,Jenkins 结合 SpringCloud+K8S,打通微服一条龙技术讲解

Jenkins 是目前最常用的持续集成工具&#xff0c;拥有近 50%的市场份额&#xff0c;他还是很多技术团队的第一个使用的自动化工具。由此可见他的重要性&#xff01; 这份 Jenkins 宝典从入门介绍到结合 DockerSpringCloudKubernetes&#xff0c;打通一条龙技术讲解&#xff0c;…