深入前端尾递归

news2024/9/24 21:22:08

在深入探讨前端尾递归前,我们先来了解递归和尾调用两个概念

递归

在函数内部调用自身,一般来说递归有两个状态

  • 递归状态(继续递归)
  • 最终状态(终止递归)

递归式方法可以被用于解决很多的计算机科学问题,因此它是计算机科学中十分重要的一个概念。绝大多数编程语言支持函数的自调用,在这些语言中函数可以通过调用自身来进行递归。计算理论可以证明递归的作用可以完全取代循环,因此在很多函数编程语言(如Scheme)中习惯用递归来实现循环。(递归算法)

在这里插入图片描述
一般而言,递归使函数看起来更加简洁,但相对循环来说不易理解。
在JavaScript语言中,函数中调用函数会形成调用栈(call stack),栈是一后进先出的结构,当函数调用链足够长时,会发生栈溢出,程序会报错,因此在使用递归时,要注意溢出的情况。

递归循环
程序简洁易阅读
栈溢出不会发生溢出

尾调用

在讨论尾调用前,我们先强调一点,尾调用的局限场景

  • safari浏览器实现了对尾调用的支持
  • 严格模式下生效

含义
函数最后一步是调用一个函数,最后一步并不指最后一行。可简单归纳为return + fn()

function bar(){}
function foo1() {
	bar()
}
// foo1不是尾调用,实际等价于
function foo1() {
	bar()
	return undefined
}

// foo2不是未调用,因为最后一步不是调用一个函数,而是 bar() + 1 求和
function foo2() {
	return bar() + 1
}

// 是尾调用
function foo3(num) {
	switch(num) {
		case 1:
			return bar()
		case 2: 
			return bar()
			
	}
}

尾调用由于最后一步是调用一个函数,所以外部函数的调用位置,内部变量不需要用到,因此不要保存外部函数的调用帧,因此执行到尾调用函数的最后一步时,内存函数的调用帧取代最外层函数的调用帧。不会发生调用栈溢出。

尾递归

尾递归,递归结合尾调用,或者说尾调用的函数是自身。

尾递归优化

前提条件

  • 严格模式
  • Safari浏览器

尾递归溢出

function sum(x, y) {
    if (y > 0) {
      return sum(x + 1, y - 1);
    } else {
      return x;
    }
}

    sum(1, 100000);

在这里插入图片描述

尾调用优化

蹦床函数

封装一个蹦床函数解决调用栈溢出

   function trampoline(f) {
      while(typeof f == 'function') {
        f = f()
      }
      return f
    }

trampoline函数接收一个函数,将递归改造成循环,我们将sum函数进行改造,返回sum函数的一个副本。

function sum(x, y) {
    if (y > 0) {
      return sum.bind(null,x + 1, y - 1);   // 这里可以不使用bind,直接返回一个函数,函数内部调用sum方法
    } else {
      return x;
    }
}
// 不使用bind



trampoline(sum(1, 100000))   // 100001

为了解决调用栈溢出的问题,我们封装了一个trampoline函数,另外对sum函数进行了改造。

tco函数

 function tco(f) {
	  let value,
	     actived = false,
	     accumulated = [];
	   return function () {
	     accumulated.push(arguments);
	     if (!actived) {
	       actived = true;
	
	       while (accumulated.length > 0) {
	         value = f.apply(null, accumulated.shift());
	       }
	       actived = false;
	       return value;
	     }
	   };
 }
  var sum = tco(function (x, y) {
     if (y > 0) {
       return sum(x + 1, y - 1);
     } else {
       return x;
     }
   });

巧妙利用accumulated,循环不会终止。每次执行到apply时,accumulated中会push进下一次参数,继续while循环。tco函数也使用了闭包。

总结

本文主要讨论了以下几个问题
1)前端中的尾递归,介绍了递归,尾调用,和尾递归三个基本概念。
2) 说明了尾调用的优化需要在严格模式下才会生效,尾调用优化在safari浏览器中得到实现。
3)递归可能造成调用栈溢出
4)可以使用循环解决调用栈溢出问题
5)封装了trampoline函数和tco函数,这两个函数中巧妙利用循环解决了调用栈溢出的问题。

参考:尾递归优化的实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/356552.html

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

相关文章

QT学习记录(六)类对象属性

类对象属性用来描述类对象的一些信息和当前的状态。类对象属性可以由类的编写者在编写类的时候定义,也可以由类的使用者在使用对象的时候定义。 由类的编写者定义 QPROPERTY()宏就是用来定义一个对象属性。 以第二行属性举例 QPROPERTY(bool enabled READ isEnabl…

Makefile基础使用和实战详解

Makefile基础使用和实战详解一、基础1.1、简单的Makefile1.2、多文件编译1.3、伪对象.PHONY二、变量2.1、自动变量2.2、特殊变量2.3、变量的类别2.4、变量及其值的来源2.5、变量引用的高级功能2.6、override 指令三、模式四、函数4.1、addprefix 函数4.2、filter函数4.3、filte…

【JavaWeb】网络层协议——IP协议

目录 IP协议结构 IP地址管理 特殊IP 解决IP地址不够用 动态分配IP地址 NAT网络地址转换 IPV6 IP协议结构 版本:就是IP协议的版本号。目前只有 4 和 6。这里介绍的是IPV4 首部长度:单位是4字节。于TCP首部长度完全一致,也是可变的&…

python--pygame实现各级菜单栏目设置

随着学期的开始,同学们也即将进入计算机相关课程的课程设计了,对于python课程设计的小伙伴,可能有些是需要利用pygame来写应该小游戏的,因为最近很多小伙伴同学也在想我要一些基于python的pygame模块做的游戏项目,此外…

SpringBoot 2.x 实战专题——SpringBoot 2.6.X版本循环依赖(内含教学视频+源代码)

SpringBoot 2.x 实战专题——SpringBoot 2.6.X版本循环依赖(内含教学视频源代码) 教学视频源代码下载链接地址:https://download.csdn.net/download/weixin_46411355/87462754 目录SpringBoot 2.x 实战专题——SpringBoot 2.6.X版本循环依赖…

React SSR

ReactDOMServer 参考链接:https://zh-hans.reactjs.org/docs/react-dom-server.html ReactDOMServer 对象允许你将组件渲染成静态标记。通常,它被使用在 Node 服务端上 // ES modules import * as ReactDOMServer from react-dom/server; // CommonJS v…

全栈之路-前端篇 | 第二讲.基础前置知识【应用服务端与编程语言】学习笔记

欢迎关注「全栈工程师修炼指南」公众号 点击 👇 下方卡片 即可关注我哟! 设为「星标⭐」每天带你 基础入门 到 进阶实践 再到 放弃学习! 涉及 企业运维、网络安全、应用开发、物联网、人工智能、大数据 学习知识 “ 花开堪折直须折,莫待无花…

谓词exsits用法及与in的使用区别

exists用法 大白话的说,exists的执行,是依次拿外层表的每条记录,去和exsits后的子查询表按你所定义的运算规则(如果有的话)做运算,如果存在结果,也就是有返回数据,无论这部分数据有…

MySQL中使用索引优化

目录 一.使用索引优化 数据准备 避免索引失效应用-全值匹配 避免索引失效应用-最左前缀法则 避免索引失效应用-其他匹配原则 1、 2、 3、 4、 5、 一.使用索引优化 索引是数据库优化最常用也是最重要的手段之一,通过索引通常可以帮助用户解决大多数的MySQL的性能优化…

Pandas库入门仅需10分钟

数据处理的时候经常性需要整理出表格,在这里介绍pandas常见使用,目录如下: 数据结构导入导出文件对数据进行操作 – 增加数据(创建数据) – 删除数据 – 改动数据 – 查找数据 – 常用操作(转置&#xff0…

Linux 配置RAID组

目录 配置RAID(软件RAID) 创建RAID组 RAID中出现坏盘如何操作 RAID 添加热备盘 删除RAID组 RAID所解决的问题 提升硬盘的I/O吞吐率 提高硬盘的读写能力 提高硬盘的安全性 进行备份 减少硬盘成本 RAID级别 存储RAID——RAID级别_静下心来敲木鱼的博…

Spring Boot中使用@Autowire装配接口是怎么回事?

在学习使用Spring Boot框架时候,发现了一个特别的现象UserMapper是一个接口,在另一个类中好像直接使用Autowired装配了一个UserMapper对象???我纳闷了一会儿,接口居然可以直接实例对象吗?根据我…

测试开发之Django实战示例 第十三章 上线

在上一章,为其他程序与我们的Web应用交互创建了RESTful API。本章将学习如何创建生产环境让我们的网站正式上线,主要内容有:配置生产环境创建自定义中间件实现自定义管理命令1创建生产环境现在该将Django项目正式部署到生产环境中了。我们将按…

深度解读依赖注入DI源码

spring-framework-5.3.10 版本依赖注入代码的入口在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean 。如果想搞清楚为什么是这里需要去学习下Bean的生命周期,这里就不介绍了。AutowiredAutowired private OrderServic…

电子科技大学操作系统期末复习笔记(五):文件管理

目录 前言 文件管理:基础 基本概念 文件 文件系统 文件系统的实现模型 文件的组成 文件名 文件分类 文件结构 逻辑结构 物理结构 练习题 文件管理:目录 文件控制块FCB FCB:File Control Block FCB信息 目录 基本概念 目…

CAN通信笔记-位时间、Tq及采样点同步

本文框架1.前言2. 位时间2.1 位时间定义2.2 位时间计算3. Tq3.1 Tq的计算3.1.1 举个例子3.2 位时间与Tq的换算4. 采样点同步4.1 硬同步4.2 重同步4.2.1 延长PBS1的重同步4.2.2 缩短PBS2的重同步1.前言 本篇记录些关于CAN的一些学习笔记,说实话CAN协议发展的已经非常…

【项目设计】—— 负载均衡式在线OJ平台

目录 一、项目的相关背景 二、所用技术栈和开发环境 三、项目的宏观结构 四、compile_server模块设计 1. 编译服务(compiler模块) 2. 运行服务(runner模块) 3. 编译并运行服务(compile_run模块) 4…

MicroBlaze系列教程(6):AXI_IIC的使用(24C04 EEPROM)

文章目录 @[toc]AXI_IIC简介MicroBlaze硬件配置常用函数使用示例波形实测参考资料工程下载本文是Xilinx MicroBlaze系列教程的第6篇文章。 AXI_IIC简介 一般情况下,使用FPGA实现I2C协议主要有两种方式:一种是基于Verilog实现起始位、停止位、ACK产生和判断、数据的发送和接收…

1.HTTP及Template介绍

目录 来源 介绍 模板与渲染 Go语言的模板引擎 模板引擎的使用 定义模板文件 解析模板文件 模板渲染 基本示例 模板语法 {{.}} 注释 pipeline 变量 移除空格 条件判断 range with 预定义函数 比较函数 自定义函数 模板的嵌套template block 修改默认的标…

加油站会员管理小程序实战开发教程13

我们上一篇讲解了会员注册的功能,本篇我们介绍一下会员开卡的功能。 会员注册之后,可以进行开卡的动作。一个会员可以有多张会员卡,在微搭中用来描述这种一对多的关系的,我们用关联关系来表达。 登录微搭的控制台,点击数据模型,点击新建数据模型 输入数据源的名称会员卡…