最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JS 递归梳理

    正文概述 掘金(nineSean)   2021-03-22   699

    原文地址

    有输入没有输出 === 白学,本文记录学习 JS 递归 -> 递归优化手段 -> 尾递归的 trampoline 实践过程。

    递归与迭代

    尤记得几年前第一次学习递归时被绕晕之后的总结:递归简言之就是自己调用自己,递出去再归回来。其实就是 2 个特征:

    • 自己调用自己的函数
    • 必须要有终止条件以退出这个循环调用

    之后接触到图灵完备的概念,了解到编程语言的设计符合图灵完备就可以映射现实,解决一切可计算的问题。简单来说一门图灵完备语言的本质只要具备顺序执行、分支、循环这几个特性即可,才恍然大悟递归与迭代其实在一定程度上是等效的。

    纯函数式语言中是没有循环语句的,而 JS 博采众长,借鉴了不同语言的特性,所以同时存在递归与循环语句两种特性。 图灵完备性无法解决不可计算的问题,如停机问题:不能判断死循环(这就是为什么死循环会卡死的原因)。 由于递归中循环调用会不停压栈,JS 引擎对最大调用栈作限制也是为了预防不可计算性问题吧。

    斐波那契数列

    循环语句

    const fibonacci = n => {
      if (n === 0 || n === 1) return n
      let i = 1,
        current,
        previous,
        next
      while (i < n) {
        previous = current || 0
        current = next || 1 
        next = current + previous
        i++
      }
      return next
    }
    

    递归

    const fibonacci = n => {
      if (n === 0 || n === 1) return n
      return fibonacci(n - 1) + fibonacci(n - 2)
    }
    

    很明显递归的写法简洁很多,但是存在调用栈过深的问题, fibonacci(100000) 直接报错栈溢出,于是出现了尾递归优化的方案。

    尾递归

    尾调用

    尾调用是在 return 关键字处的函数调用,即尾部调用,形如以下:

    const foo = () => {
      // do something 
    }
    const fn = () => {
      // do something
      return foo()       // line 6
    }
    const result = fn()  // line 8
    

    代码执行不同阶段的调用栈状况如下:

    JS 递归梳理 当执行至 line 6 时,函数 fn 的主逻辑已经完毕,fn 执行上下文唯一的作用就是记录出栈时要回到的位置:line 8,完全可以优化掉这一层,如下:

    JS 递归梳理

    尾递归

    尾递归就是使用尾调用的递归了,作用是优化掉过深的调用栈,改写斐波那契数列为尾递归:

    const fibonacci = (n, item0 = 0, item1 = 1) => {
      if (n === 0) return 0
      if (n === 1) return item1
      return fibonacci(n - 1, item1, item0 + item1)
    }
    

    斐波那契数列尾递归分析:斐波那契数列第 n 项的值为前两项之和,反过来想,我只要从第 0 项经过 n 次迭代就能得出第 n 项的值。参数一为数列中的第几项,参数二与参数三为连续的两项(从第 0 第 1 项开始),每一次尾调用迭代一次连续的两项,以求第 5 项为例图示:

    JS 递归梳理 递归 -> 尾递归思路:想办法把函数的执行逻辑通过参数传递给下一次调用,对于不同的逻辑得思考如何抽象,抽象能力需要大量练习。 由于:

    JS 引擎如果不对尾调用优化,难道就没办法愉快地使用尾递归了么?不,还有办法。

    Trampoline

    Trampoline(蹦床?)可以自动消费经过尾递归改造的函数,目的就是为了消除过多的调用栈,如下:

    const trampoline = fn => (...args) => {
      let result = fn(...args)
      while (typeof result === 'function') {
        result = result()
      }
      return result
    }
    
    const fibonacci = (n, item0 = 0, item1 = 1) => {
      if (n === 0) return 0
      if (n === 1) {
        debugger
        return item1
      }
      return () => fibonacci(n - 1, item1, item0 + item1) // 尾递归此处包一层函数再返回
    }
    
    const f = trampoline(fibonacci)
    
    console.log(f(1000000)) // 不会报错栈溢出
    

    总结

    可以使用 trampoline 包装尾递归优化栈溢出问题,但有些逻辑把递归改造为尾递归可能要思虑良久。

    循环与递归一定程度上是等效的,只是分别适用于不同的场景,有选择的使用,能愉快地编码就行了。

    参考

    Javascript中的尾递归及其优化

    什么是图灵完备

    尾调用优化


    起源地下载网 » JS 递归梳理

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元