为什么被setTimeout调用的函数没有调用栈限制?

11
timer =  window.setTimeout(function () {
    //do something
    window.setTimeout(arguments.callee, 1000);
}, 1000);

结果是这些代码运行良好。

但为什么不会导致以下错误?

最大调用栈大小超出

在调试中,发现变量作用域不包括之前执行的setTimeout函数的作用域。

谁能解释这个问题?最好附带文档。


1
这是一个递归调用,它是自我引用的。顺便提一下 - 如果您想要一个函数表达式并想要递归调用它,请不要使用 arguments.callee - 给它命名为:setTimeout(function foo(){ setTimeout(foo,1000);}) - Benjamin Gruenbaum
1
是的,arguments.callee在未来会被弃用。但是在屏幕上,使用new Function(){}时,你必须使用arguments.callee来获取函数。 - SKing7
2个回答

13

setTimeout是异步的(它在执行回调函数之前就返回),回调函数将在新的、空的调用栈帧上执行。这就是整个目的。它永远不会溢出堆栈。

它不是递归调用,对于递归调用需要保留作用域(在非尾调用优化函数的情况下)。但这也意味着函数变成了阻塞式的,这不是你想要的。


1
哦,他在问为什么它不会导致堆栈溢出。我完全没注意到。你可能需要提到,在具有异步堆栈跟踪的新版本chrome中,你可以获取堆栈跟踪信息。 - Benjamin Gruenbaum
你有文件可以解释你所说的吗? - SKing7
2
@SKing7,有DOM计时器API来指定它的工作方式,我可以给你发送一个在Chrome中实现的链接-但这并没有太大的帮助。http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html 这里是计时器API。 - Benjamin Gruenbaum
1
@SKing7:我刚刚发现了https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/EventLoop。如果你正在寻找官方的东西,HTML5计时器规范也可能能够解释这个问题,但只是用非常复杂的术语。 - Bergi

3
这是因为您假设Timeout回调存储在堆栈中,实际上它们在等待着在它自己的堆栈中执行时排队。在您的代码中,当前一个执行完成后填充队列,因此队列不会增长。
更新:您可以在这里查看规格,但我会复制文本:
setTimeout()方法必须执行以下步骤:
  1. 让handle成为大于零的用户代理定义的整数,该整数将标识要由此调用设置的超时。
  2. 向处理句柄的活动超时列表添加条目。
  3. 获取活动超时列表中的定时任务句柄,并将任务作为结果。
  4. 获取超时时间,并将超时作为结果。
  5. 如果当前运行的任务是由setTimeout()方法创建的任务,并且超时小于4,则将超时增加到4。
  6. 返回句柄,然后异步继续运行此算法。
  7. 如果方法上下文是窗口对象,请等待与方法上下文关联的文档完全激活另外的超时毫秒数(不一定连续)。
  8. 否则,如果方法上下文是WorkerUtils对象,请等待工作器未挂起的超时毫秒数(不一定连续)。
  9. 否则,请按照定义WindowTimers接口由其他对象实现的规范中所述进行操作。
  10. 等待任何在此之前启动且超时等于或小于此超时的此算法的调用已完成。
  11. 可选地,再等待用户代理定义的时间长度。


你有相关的文档可以解释你所说的吗? - SKing7

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接