说明
程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。
对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。
不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。
1:首先看一个实例(当然你可以使用更容易的方式实现,这里我们仅探讨递归)
function isEven(num) {if (num === 0) {return true;}//web 前端中文站:www.lisa33xiaoq.netif (num === 1) {return false;}//www.lisa33xiaoq.netreturn isEven(Math.abs(num) - 2);}//Outputs:true console.log(isEven(10));//Outputs:false console.log(isEven(9));
上面的程序,看起来没有任何问题,运行几次也看不出来有什么问题。然而当我们让上面的参数改为 10000 的时候,会发现有内存泄漏(堆栈溢出)问题。
//不同的 javascript 引擎报错可能不同
//Outputs:
Uncaught RangeError: Maximum call stack size exceeded
console.log(isEven(10000));
原因是每次执行代码时,都会分配一定尺寸的栈空间(Windows 系统中为 1M),每次方法调用时都会在栈里储存一定信息(如参数、局部变量、返回值等等),这些信息再少也会占用一定空间,成千上万个此类空间累积起来,自然就超过线程的栈空间了。那么如何解决此类问题?
2:使用闭包
function isEven(num) {
if (num === 0) {
return true;
}
if (num === 1) {
return false;
}
//web 前端中文站 lisa33xiaoq.net
return function() {
return isEven(Math.abs(num) - 2);
}
}
//Outputs: true console.log(isEven(4)()());
此时每次调用时,返回一个匿名函数,匿名函数执行相关的参数和局部变量将会释放,不会额外增加堆栈大小。
3:优化调用
上例调用比较麻烦,优化如下:
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));
通过上面的两种办法,都可以解决这类 js 递归函数造成的内存泄漏(堆栈溢出)问题。
