JavaScript:settimeout递归无限堆栈增加?

5

我的目标是使用HTML/CSS/JS制作背景图像的幻灯片。我找到的许多解决方案都推荐类似以下的代码:

my_recursion();

function my_recursion () {
 // cycle the Background image ...
 setTimeout(my_recursion, 3000);
}

我是否错误地认为这是不好的风格?我期望在例如第1000个循环时,其他999个my_recursion实例仍然处于打开/堆栈状态?这难道不会创建无限的堆栈并消耗更多的内存吗?

或者有一些智能参与其中,例如“如果一个函数在结尾调用自身,则第(n-1)个函数将被销毁,包括其中分配的所有变量”?


2
堆栈上只有一个 my_recursion 的条目。第一次执行在启动第二次执行之前完全完成。 - VLAZ
可能不完全相同,但我之前已经写过关于调用堆栈、递归和setTimeout(作为与队列交互的机制)的文章。 - VLAZ
1
@Robert,对于 setTimeout() 的调用会立即返回。系统会跟踪挂起的计时器,并在时间到达时调用回调函数。 - Pointy
@Robert 它被执行,但setTimeout将在队列上安排下一次执行,在当前执行结束之后再进行。 - VLAZ
2
@Robert,不行。我建议您查看我链接的其他问题,并可能进一步研究事件队列。如果您的函数仍在3秒内运行,则不会有其他任何内容正在运行。只有当它完成时,任何其他计划的代码才会运行-您将不会得到两个并行执行。 - VLAZ
显示剩余5条评论
4个回答

5

以下代码不会导致无限堆栈增加,因为setTimeout的工作方式,而且在我看来这不是坏的风格。

setTimeout不能保证代码将直接在给定的时间后运行。相反,在超时之后,它将把回调推到一个“队列”中,在堆栈为空时处理该队列。所以只有当my_recursion返回并且堆栈为空时才会运行。

如果函数在末尾调用自身(...)

my_recursion没有在任何地方调用自身。它只是将自身作为参数传递给setTimeout。之后,它将继续执行,在直接返回后从堆栈弹出。

本演示说明了堆栈和事件队列。


这是否意味着,如果我在setTimeout后紧接着有代码需要执行时间超过3秒(例如4秒),那么下一个函数会在前一个(n-1)仍在运行的同时开始并行执行吗?还是它意味着下一个实例要等待4秒才能开始,因为它无法在3秒后启动,由于第(n-1)个函数仍在运行? - Robert
我认为函数不需要立即和明确地调用自身才能成为递归。只要它导致了同一函数的另一个执行,它就应该是递归的。如果仅需要直接和明确的调用,则在考虑函数是否递归时,尾调用优化(TCO)将很重要。 - VLAZ
@Robert:不,这段代码不会并行执行。setTimeout不能保证代码会在给定的超时时间后立即运行。相反,在超时后,它会将回调函数推入一个“队列”中,在堆栈为空时进行处理。因此,它只会在my_recursion返回并且堆栈为空时才运行。 - Rengers
我已经添加了一个链接到JSConf的演示文稿,其中解释了这个问题。 - Rengers
@vlaz:你说得对,我已经更新了我的答案以更加准确。 - Rengers

2
在你的问题中,你的函数没有任何参数。在实际实现中,我希望你打算使用它们。

const cycleBackground = (elem, bgs = [], ms = 1e3, i = 0) =>
  ( elem.setAttribute ('style', bgs[i])
  , setTimeout
      ( cycleBackground      // function to schedule
      , ms                   // when to schedule, ms from now
      , elem                 // user-specified element to change
      , bgs                  // user-specified backgrounds
      , ms                   // user-specified delay
      , (i + 1) % bgs.length // next background index
      )
  )

const backgrounds =
  [ "background-color: red;"
  , "background-image: linear-gradient(45deg, cyan 0%, purple 75%);"
  , "background-color: green;"
  ]

// call site
cycleBackground
  ( document.body // element to target
  , backgrounds   // list of backgrounds
  , 3e3           // delay, 3 seconds
  )
p {
  text-align: center;
  font-size: 3vw;
  font-weight: bold;
  color: white;
}
<p>Wait 3 seconds...</p>


0

添加到 https://dev59.com/_bHma4cB1Zd3GeqPMX1Z#54443904。 想提供一些证据。 在 Node 14 上运行了以下代码。

test.js:

let i = 10;
const canThisOverflow = () => {
    i--;
    console.trace();
    if (i > 0) setTimeout(canThisOverflow, 1);
}
canThisOverflow();

输出:栈大小不增加

Trace
    at canThisOverflow (/Users/arjunmalik/Shipsy/query-builder/test.js:4:10)
    at Object.<anonymous> (/Users/arjunmalik/Shipsy/query-builder/test.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
    at internal/main/run_main_module.js:17:47
Trace
    at Timeout.canThisOverflow [as _onTimeout] (/Users/arjunmalik/Shipsy/query-builder/test.js:4:10)
    at listOnTimeout (internal/timers.js:554:17)
    at processTimers (internal/timers.js:497:7)
Trace
    at Timeout.canThisOverflow [as _onTimeout] (/Users/arjunmalik/Shipsy/query-builder/test.js:4:10)
    at listOnTimeout (internal/timers.js:554:17)
    at processTimers (internal/timers.js:497:7)

test2.js:

let i = 10;
const canThisOverflow = () => {
    i--;
    console.trace();
    if (i > 0) canThisOverflow();
}
canThisOverflow();

输出:堆栈大小增加
Trace
    at canThisOverflow (/Users/arjunmalik/Shipsy/query-builder/test2.js:4:10)
    at Object.<anonymous> (/Users/arjunmalik/Shipsy/query-builder/test2.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
    at internal/main/run_main_module.js:17:47
Trace
    at canThisOverflow (/Users/arjunmalik/Shipsy/query-builder/test2.js:4:10)
    at canThisOverflow (/Users/arjunmalik/Shipsy/query-builder/test2.js:5:13)
    at Object.<anonymous> (/Users/arjunmalik/Shipsy/query-builder/test2.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
    at internal/main/run_main_module.js:17:47
Trace
    at canThisOverflow (/Users/arjunmalik/Shipsy/query-builder/test2.js:4:10)
    at canThisOverflow (/Users/arjunmalik/Shipsy/query-builder/test2.js:5:13)
    at canThisOverflow (/Users/arjunmalik/Shipsy/query-builder/test2.js:5:13)
    at Object.<anonymous> (/Users/arjunmalik/Shipsy/query-builder/test2.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
    at internal/main/run_main_module.js:17:47

0

代码没问题。它会在第一次调用时销毁所有变量,然后使用 setTimeout() 等待下一个 function 并最终返回。你的函数没有返回下一个。

my_recursion();

function my_recursion () {
 // cycle the Background image ...
 setTimeout(my_recursion, 3000); //Sets timeout for next function.
 //returns undefined here
} 

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