JavaScript: 为什么递归不会停止?

JavaScript: Why the recursion won't stop?

如果我将 foo++ 包装在 setTimeout 中,为什么递归不会停止?我很确定我错过了关于 异步 操作的主要 JavaScript 概念。

let foo = 0;

const bar = () => {
  setTimeout(() => foo++);

  if (foo <= 2) {
    bar();
  }
}

bar();

当您调用 bar() 时,它会添加到名为 call stack 的对象中。调用堆栈用于跟踪我们在调用函数时在脚本中的位置和 return。当一个函数被调用时,它会被添加到调用堆栈中,当它 return 时,它会从调用堆栈中弹出。

Stack:
- bar()

bar() 运行时,它调用 setTimeout() 并添加到调用堆栈中。

setTimeout() 函数启动 Web API 和 finishes/returns,将其从调用堆栈弹出。 Web API 然后等待 0 毫秒(0 毫秒,因为当没有延迟传递给 setTimeout 它默认为 0)并且 pushes/enqueues 你的 () => foo++ 回调到称为任务队列的东西。

Task queue: (front ---- back)
() => foo++

仅当调用堆栈为空时,任务队列中的任务才会 popped/dequeued 通过 事件循环 从队列中移出。这很重要,因为这意味着上面增加 foo 的回调只会在 bar() 被 returned 时调用(因此将其从调用堆栈中弹出),但是,这永远不会发生,因为 bar() 继续不断地调用自己,因为您的 if 条件将始终为真,因此,将继续将 bar() 添加到调用堆栈中。

Stack:
- bar() // after first recursive call
- bar()

当您继续在递归函数中调用 bar() 时,您的调用堆栈开始填满,您的任务队列也是如此:

Stack:
- bar() // after N recursive calls
...
- bar()
- bar()

由于您的调用堆栈从来没有机会从堆栈中弹出 bar(),它会继续增长,给您一个“超出最大调用堆栈大小”的错误。

您应该将递归放在 setTimeout 回调中:

let foo = 0;

const bar = () => {
  foo++;
  if (foo <= 2) setTimeout(bar);
};

bar();

console.log(foo); // Displays 3

停止使用全局状态和副作用,您的问题就会消失 -

const delay =
 500

function bar (foo = 0)
{ if (foo > 2)
    return
  else
    setTimeout(_ => bar(foo + 1), delay)
  console.log(foo)
}

bar()