Javascript 递归调用会导致调用堆栈溢出,但不会导致事件队列溢出?

Javascript 递归调用会导致调用堆栈溢出,但不会导致事件队列溢出?,javascript,events,Javascript,Events,我最近遇到了以下代码片段: 如果数组列表太大,以下递归代码将导致堆栈溢出。如何修复此问题并仍然保留递归模式 var list = readHugeList(); var nextListItem = function() { var item = list.pop(); if (item) { // process the list item... nextListItem(); } }; 答案是: va

我最近遇到了以下代码片段:

如果数组列表太大,以下递归代码将导致堆栈溢出。如何修复此问题并仍然保留递归模式

  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时,如果项不为null,则超时函数(nextListItem)将被推送到事件队列,并且该函数将退出,从而使调用堆栈保持清除状态。当事件队列运行其超时事件时,将处理下一项,并将计时器设置为再次调用nextListItem。因此,该方法从头到尾都在处理,不需要直接递归调用,因此无论迭代次数多少,调用堆栈都保持清晰


那么,调用堆栈的开销是由事件队列处理的?是否存在事件队列不能接受更多事件的最大事件数,以及该数量与调用堆栈可以堆栈的函数调用限制相比如何?另外,是否有其他方法可以实现同样的效果?

当您进行递归函数调用时,堆栈上会出现一个新的帧:

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();
}
那么,调用堆栈的开销是由事件队列处理的

不完全是。一次只有一个事件排队。不同之处在于,它忘记了计划从何处开始——如果必须保留这些信息,它也会耗尽内存

对于递归,编译器/解释器可能会做类似的事情,将不再需要的调用堆栈丢弃。例如,Safari浏览器已经实现了这一点

还有,有没有其他方法可以实现同样的目标

当然是一个循环:-)

如何修复此问题并仍然保留递归模式

  var list = readHugeList();
  var nextListItem = function() {
      var item = list.pop();

      if (item) {
          // process the list item...
          nextListItem();
      }
  };
您可以使用手动执行TCO

function call(run) {
    return { done: false, run };
}
function stop(value) {
    return { done: true, value };
}
function runTrampoline(cont) {
    while (!cont.done) cont = cont.run();
    return cont.value;
}

var list = readHugeList();
function nextListItem() {
    return runTrampoline(nextListItemT());
}
function nextListItemT() {
    var item = list.pop();
    if (item) {
        // process the list item...
        return call(nextListItemT);
    } else {
        return stop();
    }
}

我不确定事件队列中的事件限制是多少,但在这个例子中,在任何给定的时间,队列中只有一个事件。是的,我无法重现这一点