我最近遇到了这段代码:
如果数组列表太大,以下递归代码将导致堆栈溢出。你如何解决这个问题并仍保留递归模式?
var list = readHugeList();
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
nextListItem();
}
};
答案是:
var list = readHugeList();
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
setTimeout( nextListItem, 0);
}
};
说明:由于事件循环处理递归而不是调用堆栈,因此消除了堆栈溢出。当nextListItem运行时,如果item不为null,则将超时函数(nextListItem)推送到事件队列并退出函数,从而使调用堆栈保持清零。当事件队列运行其超时事件时,将处理下一个项目并设置计时器以再次调用nextListItem。因此,该方法从开始到结束处理而没有直接递归调用,因此无论迭代次数如何,调用堆栈都保持清晰。
那么,调用堆栈的开销是由事件队列处理的?是否有最大数量的事件,在此事件之后事件队列不能再接受任何事件,并且该数字与调用堆栈的可以堆栈的函数调用的限制相比如何?另外,还有另一种方法可以实现同样的目标吗?
答案 0 :(得分:2)
那么,调用堆栈的开销是由事件队列处理的吗?
不完全是。一次只排队一个事件。不同之处在于它忘记了计划的位置 - 如果必须保留该信息,它也会耗尽内存。
对于递归,编译器/解释器可能会做类似的事情,将不再需要的调用堆栈丢弃为tail call optimisation。例如,Safari浏览器已经实现了这一点。
此外,还有另一种方法可以实现同样的目标吗?
循环,当然: - )
declare @T table (storename varchar(20), ID varchar(10));
insert into @T values ('fruit', '0');
insert into @T
select select Store_Desc + ' (' + Store_ID + ')', ID
from table_name;
select storename
from @T
order by ID;
如何解决此问题仍然保留递归模式?
您可以使用trampolining手动执行TCO。
function nextListItem = function() {
for (var item = list.pop(), item, item = list.pop()) {
// process the list item...
}
}
答案 1 :(得分:1)
当你进行递归函数调用时,一个新的框架会进入堆栈:
function foo() {
foo();
}
这将永久推送帧,直到分配给堆栈的内存耗尽为止。
但是使用setTimeout:
function foo() {
setTimeout(foo, 0);
}
在排队呼叫foo
之前删除旧的堆栈帧。
这样想:
var stack = [];
// fails eventually
function foo() {
stack.push({});
foo();
}
function timedOut() {
stack.pop(); // this makes it all ok
fooWithTimeout();
}
function fooWithTimeout() {
stack.push({});
timedOut();
}