setTimeout调用是否清除调用堆栈?

15
使用setTimeout方法调用函数而不是直接调用,能否在 JavaScript 中避免堆栈溢出?我理解 setTimeout 应该会启动一个新的调用堆栈。但是当我查看 Chrome 和 IE 的调用堆栈时,似乎 setTimeout 调用正在等待函数调用返回。
这是否只是调试器的属性,还是我的理解有误?
编辑
尽管下面提供的答案是正确的,但我实际遇到的问题与我调用setTimeout(aFunction(), 10)有关,因为由于括号的原因,aFunction 立即被评估。这个问题已经在此问题中得到解决。

传递到 setTimeout 的函数在调用 setTimeout 的函数返回之前不能被调用。因此,是的,该函数会启动一个新的调用堆栈。 - Šime Vidas
3个回答

18

我可以确认堆栈已经被清空。

考虑以下情况:

function a() {
     b();   
}

function b() {
     c();   
}

function c() {
    debugger;
    setTimeout( d, 1000 );
}

function d() {
    debugger;
}

a();

所以有两个断点 - 一个在函数 c 的开头,另一个在函数 d 的开头。

第一个断点时的堆栈:

  • c()
  • b()
  • a()

第二个断点时的堆栈:

  • d()

在线演示:http://jsfiddle.net/nbf4n/1/


谢谢你指出正确的方向,我已经根据你的回答解决了问题。然而我有另外一个问题,请参见https://dev59.com/Vl3Ua4cB1Zd3GeqP8woz - Aran Mulholland
如果这个人是正确的,那么你的答案怎么可能是正确的呢?因为这个人说它总是先将你的回调推到回调队列中,而不是栈中,所以如果在调用堆栈中仍有同步命令运行,或者堆栈中有其他东西,它必须等待更长的时间才能超过指定的毫秒数。所以这不总是一对一的。这种情况可能发生:嘿,我给了你5秒的延迟,为什么你要在7秒钟内回来?因为回调需要等待更长的时间。https://youtu.be/8aGhZQkoFbQ?t=782 - PositiveGuy
你通过 setTimeout 的第二个参数指定的延迟并不保证回调函数会在那个具体的时间点被调用,这是事实;但我的答案并没有说它会。 - Šime Vidas
我认为需要注意的是,d()将在单独的调用堆栈中使用c()的范围进行调用。因此,尽管c()已经完成了所有工作,直到d()执行时到达a(),它们的本地变量仍然保存在内存中,直到d()完成并且GC开始清除空间。 - Evan Kennedy

5

异步调用(例如来自setTimeout的调用)确实会生成一个新的调用堆栈。

当您说“当我查看Chrome和IE的调用堆栈时,似乎setTimeout调用正在等待函数调用返回,”您描述的不是非常清楚。但是,您可以在setTimeout调用的函数内部设置断点,并查看调用堆栈为空。


你知道为什么我在调试器中仍然可以看到整个调用堆栈吗?是因为我的setTimeout函数调用使用闭包来获取某些局部变量吗? - Aran Mulholland
1
@AranMulholland,你在哪里调用调试器?是在传递给setTimeout的函数内部吗? - Šime Vidas
根据问题,我只是在浏览器调试器(Chrome和IE)中查看调用堆栈。 - Aran Mulholland
如果你说它们生成一个新的调用堆栈,那么这个人说它可能仍然需要等待回调队列,他是错的吗? youtu.be/8aGhZQkoFbQ?t=782 - PositiveGuy
调用栈和回调(任务)队列是完全不相关的概念。 - Domenic

0

有另一种方法可以清除调用堆栈,而不使用setTimeout():

testLoop.js

let counter = 0;
const max = 1000000000n  // 'n' signifies BigInteger
Error.stackTraceLimit = 100;

const A = () => {
  fp = B;
}

const B = () => {
  fp = A;
}

let fp = B;

const then = process.hrtime.bigint();

loop:   // label is not needed for this simple case though useful for some cases
for(;;) {
  counter++;
  if (counter > max) {
    const now = process.hrtime.bigint();
    const nanos = now - then;

    console.log({ "runtime(sec)": Number(nanos) / (1000000000.0) })
    throw Error('exit')
  }
  fp()
  continue loop;
}

输出:

$ node testLoop.js
{ 'runtime(sec)': 20.647399601 }
C:\Users\jlowe\Documents\Projects\clearStack\testLoop.js:25
    throw Error('exit')
    ^

Error: exit
    at Object.<anonymous> (C:\Users\jlowe\Documents\Projects\clearStack\testLoop.js:25:11)
    at Module._compile (internal/modules/cjs/loader.js:776:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:787:10)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at Function.Module._load (internal/modules/cjs/loader.js:585:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:829:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)

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