Javascript中递归造成的堆栈溢出及解决方案

关于堆栈的溢出问题,在Javascript日常开发中很常见,Google了下,相关问题还是比较多的。本文旨在描述如何解决此类问题。 首先看一个实例(当然你可以使用更容易的方式实现,这里我们仅探讨递归):

  1. function isEven (num) {
  2.     if (num === 0) {
  3.         return true;
  4.     }

  5.     if (num === 1) {
  6.         return false;
  7.     }

  8.     return isEven(Math.abs(num) - 2);
  9. }

  10. //Outputs: true
  11. console.log(isEven(10));

  12. //Outputs: false
  13. console.log(isEven(9));
复制代码

当我们把参数改成10000时,运行下例会发生堆栈溢出:

  1. function isEven (num) {
  2.     if (num === 0) {
  3.         return true;
  4.     }

  5.     if (num === 1) {
  6.         return false;
  7.     }

  8.     return isEven(Math.abs(num) - 2);
  9. }

  10. //不同的javascript引擎报错可能不同
  11. //Outputs: Uncaught RangeError: Maximum call stack size exceeded
  12. console.log(isEven(10000));
复制代码

原因是每次执行代码时,都会分配一定尺寸的栈空间(Windows系统中为1M),每次方法调用时都会在栈里储存一定信息(如参数、局部变量、返回值等等),这些信息再少也会占用一定空间,成千上万个此类空间累积起来,自然就超过线程的栈空间了。那么如何解决此类问题?
使用闭包:

  1. function isEven (num) {
  2.     if (num === 0) {
  3.         return true;
  4.     }

  5.     if (num === 1) {
  6.         return false;
  7.     }

  8.     return function() {
  9.         return isEven(Math.abs(num) - 2);
  10.     }
  11. }
  12. //Outputs: true
  13. console.log(isEven(4)()());
复制代码

此时每次调用时,返回一个匿名函数,匿名函数执行相关的参数和局部变量将会释放,不会额外增加堆栈大小。
优化调用:
上例调用比较麻烦,优化如下:

  1. function isEven (num) {
  2.     if (num === 0) {
  3.         return true;
  4.     }

  5.     if (num === 1) {
  6.         return false;
  7.     }

  8.     return function() {
  9.         return isEven(Math.abs(num) - 2);
  10.     }
  11. }

  12. function trampoline (func, arg) {
  13.     var value = func(arg);

  14.     while(typeof value === "function") {
  15.         value = value();
  16.     }

  17.     return value;
  18. }
  19. //Outputs: true
  20. console.log(trampoline(isEven, 10000));

  21. //Outputs: false
  22. console.log(trampoline(isEven, 10001));
复制代码
现在我们可以解决堆栈溢出问题了,但是不是感觉每次tarmpoline(isEven, 1000)这种调用方式不是很好,我们可以使用bind来绑定:

  1. function isEven(n) {
  2.     /**
  3.      * [isEvenInner 递归]
  4.      * @param  {[type]}  num [description]
  5.      * @return {Boolean}     [description]
  6.      */
  7.     function isEvenInner (n) {
  8.         if (n === 0) {
  9.             return true;
  10.         }

  11.         if (n === 1) {
  12.             return false;
  13.         }

  14.         return function() {
  15.             return isEvenInner(Math.abs(n) - 2);
  16.         }
  17.     }
  18.     /**
  19.      * [trampoline 迭代]
  20.      * @param  {[type]} func [description]
  21.      * @param  {[type]} arg  [description]
  22.      * @return {[type]}      [description]
  23.      */
  24.     function trampoline (func, arg) {
  25.         var value = func(arg);

  26.         while(typeof value === "function") {
  27.             value = value();
  28.         }

  29.         return value;
  30.     }

  31.     return trampoline.bind(null, isEvenInner)(n);
  32. }
  33. //Outputs: true
  34. console.log(isEven(10000));

  35. //Outputs: false
  36. console.log(isEven(10001));
复制代码

虽然上例实现了我们想要的效果,但是trampoline函数还是有一定的局限性:


1.假设你只传递一个参数给递归函数


value = func(arg); 修改为 value = func.apply(func, arg);


2.假设最后的返回值不是一个函数 关于更健壮性的实现,请看underscore-contrib中源码。


转载声明:


本文标题:Javascript中递归造成的堆栈溢出及解决方案
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 递归是一种编程技巧,其一个函数调用自身。在 JavaScript 递归方法可以用来解决复杂的问题,并使代码更简洁易读。 递归的基本模型包括以下几个步骤: 1. 定义递归终止条件:当递归达到一定的深度时,必须终止递归。 2. 分治:将问题分解为规模更小的子问题。 3. 调用自身:通过解决子问题来解决整个问题。 4. 记录结果:在递归的过程,记录每一步的结果。 下面是一个简单的递归例子,计算阶乘: ``` function factorial(n) { if (n === 0) { return 1; } return n * factorial(n - 1); } ``` 在这个例子递归终止条件是 `n` 为 0,分治的子问题是计算 `n-1` 的阶乘,调用自身的方式是通过函数 `factorial` 调用,并将计算结果返回。 ### 回答2: 递归是一种在编程常见的技巧,特别在JavaScript递归方法经常被用来解决问题。递归是指一个函数调用自身的过程。下面是关于JavaScript递归方法的一些重要信息: 1. 递归方法必须包含一个终止条件,否则函数将无限地调用自身。这个终止条件通常是某个特定条件的判断,一旦足了该条件,递归将停止。 2. 递归方法必须能够将问题分解为更小的子问题,同时保持问题的解决方案是相同的。 3. 递归方法可以方便地解决一些复杂的问题,例如树和图的遍历,阶乘计算等。 4. 使用递归方法时需要小心,因为它可能会导致性能问题。递归过程使用大量的函数调用,可能导致堆栈溢出。 5. 尽管递归方法可以让代码更简洁和易于理解,但在某些情况下,使用循环可能更高效和可维护。因此,在选择使用递归方法之前,需要仔细权衡。 总的来说,JavaScript递归方法是一种强大而灵活的编程技巧,可以解决各种问题。但在使用时需要注意终止条件和性能问题,以确保代码的正确性和效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值