我发现了以下问题here:
如果数组,以下递归代码将导致堆栈溢出 列表太大了。你如何解决这个问题并保持递归 图案?
答案是:
通过修改可以避免潜在的堆栈溢出 nextListItem函数如下:
var list = readHugeList();
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
setTimeout( nextListItem, 0);
}
};
由于事件循环处理了堆栈溢出,因此消除了堆栈溢出 递归,而不是调用堆栈。当nextListItem运行时,如果item不是 null,超时函数(nextListItem)被推送到事件队列 并且函数退出,从而使调用堆栈清晰。当。。。的时候 事件队列运行其超时事件,处理下一个项目并且a timer设置为再次调用nextListItem。因此,该方法是 从头到尾处理没有直接递归调用,所以 无论迭代次数多少,调用堆栈都会保持清晰。
有人可以向我解释一下:
答案 0 :(得分:7)
这只是trampolines的一种替代方案,而TCO只是一种替代event loop的hacky替代方案。
在Javascript中调用函数时,会向调用堆栈添加一个框架。该框架包含有关函数范围内的变量及其调用方式的信息。
在调用函数之前,调用堆栈是空的。
-------
如果我们调用函数foo
,那么我们将新的框架添加到堆栈的顶部。
| foo |
-------
当foo
完成执行时,我们再次将帧从堆栈中弹出,再将其留空。
现在,如果foo
依次调用另一个函数bar
,那么我们需要在堆栈中添加一个新帧,而foo
正在执行。
| bar |
| foo |
-------
希望您可以看到,如果函数以递归方式调用自身,它会不断将新帧添加到调用堆栈的顶部。
| ... |
| nextListItem |
| nextListItem |
| nextListItem |
| nextListItem |
----------------
递归函数将继续添加帧,直到它们完成处理,或者它们超过调用堆栈的最大长度,从而导致溢出。
因为setTimeout
是一个异步操作,它不会阻止你的函数,这意味着nextListItem
将被允许完成,它的框架可以从调用堆栈弹出 - 防止它增长。递归调用将使用depends on your browser来处理。
这个模式是否有用?调用堆栈{{3}}的最大大小,但它可以低至1130.如果你想处理一个包含几千个元素的数组使用递归函数,那么你冒着吹掉调用堆栈的风险。
Trampolines使用类似的技术,但不是将工作卸载到事件循环,而是返回一个调用下一次迭代的函数,然后可以使用while循环(不影响堆栈)来管理调用
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
return nextListItem;
}
};
while(recur = recur()) {}
答案 1 :(得分:2)
通常情况并非如此,但是如果你决定需要以递归方式为长序列链接相同的函数调用,这可能会派上用场。
在完全利用为特定程序分配的堆栈内存量时,会发生递归操作期间的堆栈溢出。递归遍历的足够长的数组可能导致堆栈溢出。也许你不明白call stack是如何工作的?
答案 2 :(得分:1)
重复for
循环对于发布的代码最有效。但是,如果您的实际代码无法使用for
循环,那么还有另一种选择。
使用setTimeout
取决于您对“实用”的看法,'所以,让我们列出事实,这样你就可以自己决定。
setTimeout
强制浏览器通过操作来淹没CPU,以获得毫秒级的精确计时。这可能是功率效率低下的。 setTimeout
,每次迭代将花费4毫秒。只需4次迭代就可以花时间渲染整个帧。 250次迭代,一整秒过去。但是setTimeout
还有一种替代方法可以帮助您在不使用setTimeout的情况下完全实现您的目标:the DeferStackJS library。如果您使用DeferStackJS,那么您需要做的就是如下。
var list = readHugeList();
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
DeferStack( nextListItem );
}
};
请强调,上面的代码片段用于演示如何集成DeferStackJS。说实话,使用DeferStackJS或Array.prototype.pop
对于这段特定的代码片段非常不合适。相反,下面的代码会击败他们两个人。
var list = readHugeList();
var nextListItem = function() {
"use strict";
var item, i = list.length;
while (i--) { // Bounds check: as soon as i === -1, the while loop will stop.
// This is because i-- decrements i, returning the previous
// value of i
item = list[i];
if (!item) break; // break as soon as it finds a falsey item
// Place code here to be executed if it found a truthy value:
// process the list item...
}
if (i !== -1) {
// Place code here to be executed if it found a falsey value:
}
};
提到DeferStackJS的唯一原因是因为我坚信,回答论坛的人的第一职责是回答原来提出的问题。然后他们回答说他们可以提出评论,然后再猜测一下这个问题是什么意思。