调用堆栈超出但Event Queue能够处理?

时间:2018-04-05 18:04:25

标签: javascript events

我最近遇到了这段代码:

如果数组列表太大,以下递归代码将导致堆栈溢出。你如何解决这个问题并仍保留递归模式?

  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。因此,该方法从开始到结束处理而没有直接递归调用,因此无论迭代次数如何,调用堆栈都保持清晰。

那么,调用堆栈的开销是由事件队列处理的?是否有最大数量的事件,在此事件之后事件队列不能再接受任何事件,并且该数字与调用堆栈的可以堆栈的函数调用的限制相比如何?另外,还有另一种方法可以实现同样的目标吗?

2 个答案:

答案 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();
}