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

function isEven (num) {
if (num === 0) {
return true;
} if (num === 1) {
return false;
} return isEven(Math.abs(num) - 2);
} //Outputs: true
console.log(isEven(10)); //Outputs: false
console.log(isEven(9));

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

function isEven (num) {
if (num === 0) {
return true;
} if (num === 1) {
return false;
} return isEven(Math.abs(num) - 2);
} //不同的javascript引擎报错可能不同
//Outputs: Uncaught RangeError: Maximum call stack size exceeded
console.log(isEven(10000));

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

使用闭包:

function isEven (num) {
if (num === 0) {
return true;
} if (num === 1) {
return false;
} return function() {
return isEven(Math.abs(num) - 2);
}
}
//Outputs: true
console.log(isEven(4)()());

此时每次调用时,返回一个匿名函数,匿名函数执行相关的参数和局部变量将会释放,不会额外增加堆栈大小。

优化调用:

上例调用比较麻烦,优化如下:

function isEven (num) {
if (num === 0) {
return true;
} if (num === 1) {
return false;
} return function() {
return isEven(Math.abs(num) - 2);
}
} function trampoline (func, arg) {
var value = func(arg); while(typeof value === "function") {
value = value();
} return value;
}
//Outputs: true
console.log(trampoline(isEven, 10000)); //Outputs: false
console.log(trampoline(isEven, 10001));

现在我们可以解决堆栈溢出问题了,但是不是感觉每次tarmpoline(isEven, 1000)这种调用方式不是很好,我们可以使用bind来绑定:

function isEven(n) {
/**
* [isEvenInner 递归]
* @param {[type]} num [description]
* @return {Boolean} [description]
*/
function isEvenInner (n) {
if (n === 0) {
return true;
} if (n === 1) {
return false;
} return function() {
return isEvenInner(Math.abs(n) - 2);
}
}
/**
* [trampoline 迭代]
* @param {[type]} func [description]
* @param {[type]} arg [description]
* @return {[type]} [description]
*/
function trampoline (func, arg) {
var value = func(arg); while(typeof value === "function") {
value = value();
} return value;
} return trampoline.bind(null, isEvenInner)(n);
}
//Outputs: true
console.log(isEven(10000)); //Outputs: false
console.log(isEven(10001));

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

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

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

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

感谢您的阅读,文中不妥之处还望批评指正,文章已同步至个人博客如果你有好的建议,欢迎留言,么么哒!

转载声明:

本文标题:Javascript中递归造成的堆栈溢出及解决方案

本文链接:http://www.zuojj.com/archives/1115.html,转载请注明转自Benjamin-专注前端开发和用户体验

Javascript中递归造成的堆栈溢出及解决方案的更多相关文章

  1. 使用es6的蹦床函数解决递归造成的堆栈溢出

      首先,我们先定义一个函数,使用递归的思想写求和的方法: function sum(x, y) { if (y > 0) { return sum(x + 1, y - 1); } else ...

  2. arcgis engine 中出现的内存堆栈溢出问题。

    两种解决方案: 1.循环加载mxd文档的时候出现的堆栈溢出,解决办法是每次循环结束时清空FeatureLayer,感觉并不好,但是确实可以实现功能. 2.循环调取featureclass的search ...

  3. javascript递归导致的堆栈溢出

    function foo() {foo(); //setTimeout(foo, 0);   } foo() 原因是每次执行代码时,都会分配一定尺寸的栈空间(Windows系统中为1M),每次方法调用 ...

  4. python递归次数和堆栈溢出问题

    在做递归的时候,测试了一下python的递归能力. 如果不设置递归次数的话,大概只能在992次左右,就会出现错误:RuntimeError: maximum recursion depth excee ...

  5. Javascript中递归的调用

    递归函数就是调用自身,如下所示: function factorial(num){ if(num<=1){ return 1; }else{ return num*factorial(num-1 ...

  6. JPA 一对多双向映射 结果对象相互迭代 造成堆栈溢出问题方法

    问题: JPA 在双向映射时,会相互包含对方的实例,相互引用,造成递归迭代,堆栈溢出(java.lang.StackOverflowError). 分析: 在后端向前端传递的时候会将数据序列化,转为j ...

  7. JavaScript中的递归

    译者按: 程序员应该知道递归,但是你真的知道是怎么回事么? 原文: All About Recursion, PTC, TCO and STC in JavaScript 译者: Fundebug 为 ...

  8. javascript中的堆栈、深拷贝和浅拷贝、闭包

    堆栈 在javascript中,堆内存是用来存放引用类型的空间环境 而栈内存,是存储基本类型和指定代码的环境 在对象中的属性名具有唯一性,数字属性名=字符串属性名,但是在测试的时候你会发现,好像所有属 ...

  9. javascript中的Array对象 —— 数组的合并、转换、迭代、排序、堆栈

    Array 是javascript中经常用到的数据类型.javascript 的数组其他语言中数组的最大的区别是其每个数组项都可以保存任何类型的数据.本文主要讨论javascript中数组的声明.转换 ...

随机推荐

  1. 抽象数据类型ADT

    ADT(Abstract Data Type) 类型由什么组成? 一个类型(type)指定两类信息,一个属性集和一个操作集. 假设要定义一个新的数据类型.首先,要提供存储数据的方式,可能是通过设计一个 ...

  2. Linux内核分析第六周学习总结:进程的描述和进程的创建

    韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.进程的描述 ...

  3. Python-实现对表插入百万条数据

    新手小白写的,我自己都不知道对不对,先写下来记着,以后掌握更多Python知识后,再来看下写的对不对. 题目:造一百万条大学生的基本信息(学校.专业.姓名.学号) 分析思路:利用Python的MySQ ...

  4. Linux系统编程-防止僵尸进程产生的常用方法

    1.父进程调用wait函数或waitpid函数回收子进程. 2.让init进程去处理子进程回收工作,代码中加上"signal(SIGCHLD, SIG_IGN)"这句话.

  5. flask--虚拟环境

    1.安装虚拟环境mosson@mosson:~$ sudo apt-get install virtualenv2.创建一个项目目录mosson@mosson:~$ mkdir myproject3. ...

  6. 图解Android Studio导入Eclipse项目源码

    方法/步骤   打开Android Studio,在主页面中选择"File"->"New"->"Import project...&quo ...

  7. iOS9支付完成无法获取回调

    - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id ...

  8. Where art thou

    function where(collection, source) { var arr = []; // What's in a name? var keys = Object.keys(sourc ...

  9. 技术英文单词贴--G

    G generator 发电机,发生器,生产者

  10. 【教程】手把手教你如何利用工具(IE9的F12)去分析模拟登陆网站(百度首页)的内部逻辑过程

    [前提] 想要实现使用某种语言,比如Python,C#等,去实现模拟登陆网站的话,首先要做的事情就是使用某种工具,去分析本身使用浏览器去登陆网页的时候,其内部的执行过程,内部逻辑. 此登陆的逻辑过程, ...