Javascript尾递归编程的实现

目录
  • 尾递归编程思想
  • 最容易的递归
  • 运用缓存结果思想解决函数开销
  • 迭代方法
  • 尾递归实现
  • 原理图解
  • 关于Javascript没有实现尾递归优化
  • trampoline实现

尾递归编程思想

递归是编程中必不可少的一环,在算法和工程上会经常使用,但是随着计算量的增大,函数堆栈会大量堆积上一函数上下文中的变量和方法,会导致主线程栈的空间不足而造成栈溢出错误,由于新的函数压入堆栈后,上一函数仍然在堆栈中未被释放,因此内存资源消耗会十分大,对性能也会有很大影响。

我们知道递归写起来确实方便,逻辑也容易理解,最简单的斐波那契数列问题,跳楼梯,一次只能1步或2步,跳n格有多少种方法

最容易的递归

// 限制条件 countOfStep>0
function jump(countOfStep) {
    if (countOfStep <= 0) return 0;
    function jumpRecursive(innerCountOfStep) {
        if (innerCountOfStep < 0) return 0;
        if (innerCountOfStep === 1 || innerCountOfStep === 0) return 1;
        return jumpRecursive(innerCountOfStep - 1) + jumpRecursive(innerCountOfStep - 2);
    }
    return jumpRecursive(countOfStep);
}

很明显上述递归没有任何优化,利用函数堆栈来实现对上一结果的保存作为下一结果的支撑,函数开销大。

运用缓存结果思想解决函数开销

function jumpWithoutFuncCost(countOfStep) {
    if(countOfStep<=0) return 0;
    const saves = new Array(countOfStep + 1).fill(0);
    [saves[0], saves[1]] = [1, 1];
    for (let i = 2; i <= countOfStep; i++) {
        saves[i] = saves[i - 1] + saves[i - 2];
    }
    return saves[countOfStep];
}

是解决了数据过大栈溢出问题了,不过也同时带来空间开销

迭代方法

function jumpIteritive(countOfStep) {
    if(countOfStep<=0) return 0;
    let [prefix, suffix] = [1, 1];
    for (let i = 2; i <= countOfStep; i++) {
        let temp = suffix;
        suffix += prefix;
        prefix = temp;
    }
    return suffix;
}

如果是复杂点的问题迭代法是比较难想出来的。但凡可以实现迭代处理的方法可以用尾递归实现,递归的实现更具有可读性和简洁性。

尾递归实现

function jumpTailRecursive(countOfStep, prev = 1, next = 1) {
    if(countOfStep<=0) return 0;
    if (countOfStep === 1) return next;
    return jumpTailRecursive(--countOfStep, next, prev + next);
}

原理图解

关于Javascript没有实现尾递归优化

Javascript由于某些原因,JavaScript引擎实现者认为特性不合理,以及各大厂商的权力纠纷问题,TC39提案仍未落实尾递归优化方案。

如果要实现JavaScript尾递归优化,需要采用蹦床函数辅助实现,才能实现和迭代一样避免栈溢出情况。

trampoline实现

function jumpTailRecursiveTrampolined(countOfStep, prev = 1, next = 1) {
    if (countOfStep <= 0) return 0;
    if (countOfStep === 1) return next;
    return () => jumpTailRecursiveTrampolined(--countOfStep, next, prev + next);
}

function trampoline(F){
    return function(...args){
        F = F.bind(this, ...args);
        while (F instanceof Function) {
            F = F();
        }
        return F;
    }
}

const uniformLog = (element) => console.log(JSON.stringify(element, undefined, 4));
uniformLog(trampoline(jumpTailRecursiveTrampolined)(3));

到此这篇关于Javascript尾递归编程的实现的文章就介绍到这了,更多相关Javascript尾递归内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JS尾递归的实现方法及代码优化技巧

    本文实例讲述了JS尾递归的实现方法及代码优化技巧.分享给大家供大家参考,具体如下: 在学习数据结构和算法的时候,我们都知道所有的递归都是可以优化成栈+循环的. 对于特定的递归函数,一般我们都是手动对它们进行优化的. 在学习scala的时候,接触到尾递归的概念.我们只要将递归写成尾递归方式,编译器会自动帮助我们优化. ps:并不是所有的递归都可以改写成尾递归 在js中,尾递归通常会被解释器优化.然而,并不是所有的js解释器都支持尾递归优化. 对于不支持尾递归优化的环境,我们需要手动将递归优化成栈+

  • 详解JavaScript调用栈、尾递归和手动优化

    调用栈(Call Stack) 调用栈(Call Stack)是一个基本的计算机概念,这里引入一个概念:栈帧. 栈帧是指为一个函数调用单独分配的那部分栈空间. 当运行的程序从当前函数调用另外一个函数时,就会为下一个函数建立一个新的栈帧,并且进入这个栈帧,这个栈帧称为当前帧.而原来的函数也有一个对应的栈帧,被称为调用帧.每一个栈帧里面都会存入当前函数的局部变量. 当函数被调用时,就会被加入到调用栈顶部,执行结束之后,就会从调用栈顶部移除该函数.并将程序运行权利(帧指针)交给此时栈顶的栈帧.这种后进

  • Javascript尾递归编程的实现

    目录 尾递归编程思想 最容易的递归 运用缓存结果思想解决函数开销 迭代方法 尾递归实现 原理图解 关于Javascript没有实现尾递归优化 trampoline实现 尾递归编程思想 递归是编程中必不可少的一环,在算法和工程上会经常使用,但是随着计算量的增大,函数堆栈会大量堆积上一函数上下文中的变量和方法,会导致主线程栈的空间不足而造成栈溢出错误,由于新的函数压入堆栈后,上一函数仍然在堆栈中未被释放,因此内存资源消耗会十分大,对性能也会有很大影响. 我们知道递归写起来确实方便,逻辑也容易理解,最

  • 再谈javascript面向对象编程

    另外这篇文章是一篇入门文章,我也是才开始学习Javascript,有一点心得,才想写一篇这样文章,文章中难免有错误的地方,还请各位不吝吐槽指正 吐槽Javascript 初次接触Javascript,这门语言的确会让很多正规军感到诸多的不适,这种不适来自于Javascript的语法的简练和不严谨,这种不适也来自Javascript这个悲催的名称,我在想网景公司的Javascript设计者在给他起名称那天一定是脑壳进水了,让Javascript这么多年来受了这么多不白之冤,人们都认为他是Java的

  • Javascript 面向对象编程(一) 封装

    学习Javascript,最难的地方是什么? 我觉得,Object(对象)最难.因为Javascript的Object模型很独特,和其他语言都不一样,初学者不容易掌握. 下面就是我的学习笔记,希望对大家学习这个部分有所帮助.我主要参考了以下两本书籍: <面向对象的Javascript>(Object-Oriented JavaScript) <Javascript高级程序设计(第二版)>(Professional JavaScript for Web Developers, 2nd

  • Javascript模块化编程详解

    模块化编程是一种非常常见Javascript编程模式.它一般来说可以使得代码更易于理解,但是有许多优秀的实践还没有广为人知. 基础 我们首先简单地概述一下,自从三年前Eric Miraglia(YUI的开发者)第一次发表博客描述模块化模式以来的一些模块化模式.如果你已经对于这些模块化模式非常熟悉了,大可以直接跳过本节,从"进阶模式"开始阅读. 匿名闭包 这是一种让一切变为可能的基本结构,同时它也是Javascript最棒的特性.我们将简单地创建一个匿名函数并立即执行它.所有的代码将跑在

  • 《JavaScript函数式编程》读后感

    本文章记录本人在学习 函数式 中理解到的一些东西,加深记忆和并且整理记录下来,方便之后的复习. 在近期看到了<JavaScript函数式编程>这本书预售的时候就定了下来.主要目的是个人目前还是不理解什么是函数式编程.在自己学习的过程中一直听到身边的人说面向过程编程和面向对象编程,而函数式就非常少.为了自己不要落后于其他同学的脚步,故想以写笔记的方式去分享和记录自己阅读中所汲取的知识. js 和函数式编程 书中用了一句简单的话来回答了什么是函数式编程: 函数式编程通过使用函数来将值转换为抽象单元

  • 浅谈javascript 面向对象编程

    感叹是为了缓解严肃的气氛并引出今天要讲的话题,"javascript面向对象编程",接下来,我们围绕面向对象的几大关键字:封装,继承,多态,展开. 封装:javascript中创建对象的模式中,个人认为通过闭包才算的上是真正意义上的封装,所以首先我们先来简单介绍一下闭包,看下面这个例子: 复制代码 代码如下: <script type="text/javascript"> function myInfo(){ var name ="老鱼&quo

  • JavaScript 面向对象编程(2) 定义类

    本文承接上一篇JavaScript面向对象编程(1) 基础. 上篇说过,JavaScript没有类的概念,需要通过函数来实现类的定义.先通过一个例子说明: 复制代码 代码如下: function myClass() { var id = 1; var name = "johnson"; //properties this.ID = id; this.Name = name; //method this.showMessage = function() { alert("ID:

  • 老鱼 浅谈javascript面向对象编程

    感叹是为了缓解严肃的气氛并引出今天要讲的话题,"javascript面向对象编程",接下来,我们围绕面向对象的几大关键字:封装,继承,多态,展开. 封装:javascript中创建对象的模式中,个人认为通过闭包才算的上是真正意义上的封装,所以首先我们先来简单介绍一下闭包,看下面这个例子: 复制代码 代码如下: <script type="text/javascript">// <![CDATA[ function myInfo(){ var nam

  • Javascript模块化编程(一)AMD规范(规范使用模块)

    这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块.  (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块. 但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套!考虑到Javascript模块现在还没有官方规范,这一点就更重要了. 目前,通行的Javascript模块规范共有两种:CommonJS和AMD.我主要介绍AMD,

  • 再谈JavaScript异步编程

    随着前端的发展,异步这个词真是越来越常见了.假设我们现在有这么一个异步任务: 向服务器发起数次请求,每次请求的结果作为下次请求的参数. 来看看我们都有哪些处理方法: Callbacks 最先想到也是最常用的便是回调函数了,我们来进行简单的封装: let makeAjaxCall = (url, cb) => { // do some ajax // callback with result } makeAjaxCall('http://url1', (result) => { result =

随机推荐