使用setTimeout

时间:2016-03-25 07:58:27

标签: javascript recursion

我发现了以下问题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。因此,该方法是   从头到尾处理没有直接递归调用,所以   无论迭代次数多少,调用堆栈都会保持清晰。

有人可以向我解释一下:

  1. 此用例是否实用
  2. 为什么长数组会导致堆栈溢出

3 个答案:

答案 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)

  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的唯一原因是因为我坚信,回答论坛的人的第一职责是回答原来提出的问题。然后他们回答说他们可以提出评论,然后再猜测一下这个问题是什么意思。