javascript尾递归优化

news2024/9/25 17:20:58

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://www.coloradmin.cn/o/376421.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ubuntu下用i686-w64-mingw32交叉编译支持SDL、Openssl的ffmpeg库

前言 本篇博客是基于前两篇关于ffmpeg交叉编译下&#xff0c;进行再次编译操作。ubuntu下ffmpeg的交叉编译环境搭建可以参看以下我的这篇博客&#xff1a;https://blog.csdn.net/linyibin_123/article/details/108759367 &#xff1b; ubuntu下交叉编译openssl及交叉编译支持o…

【微信小程序】-- WXML 模板语法 - 事件绑定 -- tap input (十)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…

模电学习8. 三极管推挽电路

模电学习8. 三极管推挽电路一、推挽的概念二、三极管基本的推挽电路1. 上N下P型电路(1) 原理图(2) 电流分析2. 上P下N型(1) 原理图(2) 电流分析三、电路仿真分析1. 测试原理图2. 简要分析三、三极管的交越失真2. 处理方式三、三极管推挽电路的缺点一、推挽的概念 在电路中&…

Android性能优化(三)—— 绘制优化

运行的 Android 手机&#xff0c;虽然配置在不断的提升&#xff0c;但是仍然无法和 PC 相比&#xff0c;无法做到 PC 那样拥有超大内存以及高性能的 CPU。因此在开发 Android 应用程序时也不可能无限制的使用 CPU 和内存&#xff0c;如果对 CPU 和 内存使用不当也会造成应用的卡…

Tomcat的部署详解(基于Centos7.9))

文章目录Tomcat的部署1.1 安装jdk1.2、安装Tomcat1.3 Tomcat的目录结构1.4 Tomcat管理Tomcat web管理功能Tomcat配置文件TomcatTomcat&#xff1a;一种web服务器 Tomacat是由Apache推出的一款免费开源的Servlet容器&#xff0c;可实现JavaWeb程序的装载。 Tomcat服务器是一个…

大数据处理各组件概念及作用

一、数据采集&#xff1a; 1.1 Flume集群&#xff1a;数据采集工具&#xff0c;如写脚本将不同源端的数据采集后进行数据存储&#xff0c;或推送至Kafka等&#xff1b; 1.2 FTP集群&#xff1a;文件传输工具&#xff1b; 1.3 Kafka集群&#xff1a;消息队列&#xff0c;未避免…

UEFI启动的七阶段笔记

研究起点是CPU收到ResetVector信号后&#xff0c;开始执行第一行代码&#xff0c;一直到计算机关机/重启/崩溃&#xff0c;被划分为七个不同阶段。 1. SEC安全阶段 最早开始运行的固件代码&#xff0c;很大部分是汇编语言开发的。 主要负责四件事&#xff1a; 处理平台所有的…

纯手动搭建大数据集群架构_记录007_搭建Zookeeper3.5.7集群_集群配置_集群脚本---大数据之Hadoop3.x工作笔记0168

然后我们来搭建一下zookeeper集群,这个集群,以后不管是搭建Hadoop高可用,还是nifi集群,还是 hive 集群,hbase集群,spark集群,kettle集群,sqoop集群,都是基础 [root@hadoop122 ~]# cd /opt/software/ [root@hadoop122 software]# ll 总用量 479040 -rw-r--r--. 1 root root 9…

时间颗粒度选择(通过选择时间范围和颗粒度展示选项)

<template><div><el-time-selectplaceholder"起始时间"v-model"startTime":picker-options"startPickerOptions"change"changeStartTime"></el-time-select><el-time-selectplaceholder"结束时间&quo…

【Python】python深拷贝与浅拷贝详解(必须掌握)

深拷贝和浅拷贝是python必须要掌握的内容&#xff0c;无论你是面试开发、测试、运维等职位&#xff0c;只要是python&#xff0c;深拷贝与浅拷贝是面试官常问的一个重要知识点。 &#xff08;关注“测试开发自动化” 弓中皓&#xff0c;获取更多学习内容&#xff09; 文章目录一…

软件测试如何进行需求分析,你真的学会了吗?

目录 前言 从宏观的角度看需求文档 从细节的角度看需求文档 1&#xff09;结构化项目流程 2&#xff09;确认影响模块 3&#xff09;考量综合因素 需求的测试成本与质量风险 总结感谢每一个认真阅读我文章的人&#xff01;&#xff01;&#xff01; 重点&#xff1a;配…

Win11安卓应用自动化测试的准备

前言 APP的自动化测试&#xff0c;前提从ADB连接设备&#xff0c;启动APK应用开始。Win 11自带的WSA的APP的测试&#xff0c;要完全自动化&#xff0c;需要解决几个问题。 1、 启动WSA 此项有两步&#xff1a; i 启动WSA服务 Python程序来启动服务&#xff0c;可以直接调用…

DP1621国产LCD驱动芯片兼容替代HT1621B

目录DP1621简介DP1621芯片特性DP1621简介 DP1621是点阵式存储映射的LCD驱动器芯片&#xff0c;可支持最大128点&#xff08;32SEG * 4COM&#xff09;的 LCD屏&#xff0c;也支持2COM和3COM的LCD屏。单片机可通过3/4个通信脚配置显示参数和发送显示数据&#xff0c;也可通过指…

jmeter基本使用

jmeter基本使用 1.jemeter基本介绍 Jmeter 是什么 Apache JMeter 是 Apache 组织基于 Java 开发的压力测试工具&#xff0c;用于对软件做压力测 试JMeter 可用于 Web 应用测试&#xff0c;后来扩展到了其他测试领域具体来说, Jmeter 可以测试静态和动态资源&#xff0c;比如…

C语言Switch语句用法

C switch 语句 一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case&#xff0c;且被测试的变量会对每个 switch case 进行检查。 语法 C 语言中 switch 语句的语法&#xff1a; switch(expression){case constant-expression :statement(s);break;…

论索引的重要性

开篇小测验下面这样一个小SQL 你该怎么样添加最优索引两个表上现在只有聚集索引bigproduct 表上已经有聚集索引 ProductIDbigtransactionhistory 表上已经有聚集索引 TransactionID你是否一眼就能看出来呢&#xff1f;答案将在文章中逐步揭晓~~~简单粗暴的添加索引首先我们看一…

企业如何做好员工安全意识提升

背景&#xff1a; 近年来随着网络安全政策、技术的不断发展&#xff0c;国内企业对于安全的重视程度越来越高&#xff0c;安全建设投入力度越来越大&#xff0c;安全防御能力得到了明显的提升。然而&#xff0c;企业面临一个尴尬的问题就是&#xff0c;企业即使做了很多安全防…

Java还值得选择吗?

自1995年Java问世&#xff0c;到2023年已经差不多存在了28年。作为高级编程语言&#xff0c;他的生命周期相比很多编程语言都长&#xff0c;也见证了很多编程语言的辉煌时刻&#xff0c;不过Java始终都是名列前茅。 Java的主要优势在于其一次编写、随处运行。简单来讲&#xf…

185、【栈与队列】leetcode ——496. 下一个更大元素 I:单调栈-哈希表(C++版本)

题目描述 原题链接&#xff1a;496. 下一个更大元素 I 解题思路 本题与 739. 每日温度 的区别在于&#xff0c;需要先通过让nums1与nums2判定出为想等元素后&#xff0c;再去找nums2中更大的数。 因此&#xff0c;第一步需要找到想等数&#xff0c;第二步需要找到大于的数。…

快手电商新增商品信息诊断规则,对商家有何影响?

1、2022年快手短剧日活跃用户达2.6亿 新榜讯 近日&#xff0c;快手数据显示&#xff0c;2022年快手短剧日活跃用户达2.6亿&#xff0c;现在的付费用户数对比2022年4月增长超过480%&#xff0c;快手已经是最大的短剧消费市场。此外&#xff0c;2023年快手小游戏日活跃用户峰值超…