为啥在循环开始时调用 requestAnimationFrame 不会导致无限递归?

Posted

技术标签:

【中文标题】为啥在循环开始时调用 requestAnimationFrame 不会导致无限递归?【英文标题】:Why does calling requestAnimationFrame at the beginning of a loop not cause infinite recursion?为什么在循环开始时调用 requestAnimationFrame 不会导致无限递归? 【发布时间】:2014-03-18 22:44:46 【问题描述】:

发生了什么让循环的其余部分执行,然后requestAnimationFrame 执行下一帧?

我误解了这种方法的工作原理,并且在任何地方都看不到清晰的解释。我尝试在这里阅读时间规范http://www.w3.org/TR/animation-timing/,但我无法弄清楚它是如何工作的。

例如,这段代码取自threejs文档。

var render = function ()  
  requestAnimationFrame(render); 
  cube.rotation.x += 0.1; 
  cube.rotation.y += 0.1;
  renderer.render(scene, camera); 
;

【问题讨论】:

你能发布一些代码吗? Request Animation Frame for Better Performance, requestAnimationFrame for Smart Animating requestAnimationFrame 不是递归的,它是异步的(就像一个短的 setTimeout 但更有效)并在调用它的函数体之后执行。但是把它放在一个没有什么意义for 循环。递归发生在渲染函数中,而不是在 requestAnimationFrame 中,您没有在渲染中调用渲染。 【参考方案1】:

出于同样的原因,在循环中使用setTimeout 调度回调不会导致无限递归,它调度 JS event loop 上的下一次调用而不是执行马上。

调用不是在当前上下文中进行的,所以从技术上讲它不是严格意义上的递归,也不会导致超出堆栈限制的错误。

(来源:dartlang.org)

这个图是针对 Dart 的,但是在 JS 中的概念是一样的。如果您有兴趣阅读有关事件循环、调度计时器以及微任务和宏任务之间的区别的更多信息,请查看this question。

【讨论】:

【参考方案2】:

会发生这样的事情:

你声明一个函数定义,调用requestAnimationFrame函数。

哪个调度你的函数在合适的时候被调用并再次执行,也就是下一帧,通常在16ms之后。此外,此调度是异步的。它不会停止执行它下面的代码。所以这行下面的代码并不是在 16 毫秒之后才能工作。

但是,在大多数情况下,函数会在 3-4 毫秒内执行。

但是如果函数需要更长的时间来完成下一帧将会被延迟,因此不会执行计划任务,即再次调用相同的函数。

从某种意义上说,动画是无限循环。 requestAnimationFrame 的目标是。但是,这种非阻塞无限循环受到帧数/fps 的限制。

【讨论】:

根据我的测试和有限的理解,raf 在第一次调用时不一定要等待下一帧。第一次它通常几乎立即运行的原因(当我在 chrome 中测试时为 0.4ms)是因为它在下一帧之前运行得尽可能快。但是当它在递归循环中被调用时,循环内的第二个和所有后续调用将被反跳到帧速率。我可能完全错了,只是我对正在发生的事情的最佳猜测。我只是很难浏览器在 0.4 毫秒内渲染一帧,尤其是。不需要的时候。 raf 试图通过运行不会显示的代码来不浪费资源。假设帧以 10 毫秒更新。并且您的代码循环大约 5 毫秒。然后你的代码告诉浏览器画两次,但它只做最后一件事,这是 raf 阻止的。如果浏览器可以更快地更新帧,那么代码运行得更快。反过来说你的代码需要 20 毫秒,而浏览器可以在 10 毫秒更新。如果没有任何改变,那么更新就没有意义了,浏览器会延迟更新。从而节省计算资源。【参考方案3】:

如果我完全不在基地,请告诉我;我以前没有用过动画的东西。我看到使用requestAnimationFrame 的一个例子是:

(function animloop()
  requestAnimFrame(animloop);
  render();
)();

您想知道为什么 animloop 在被传递到 requestAnimFrame 时不会在随后被调用时导致无限循环吗?

这是因为这个函数真正不是递归的。您可能会认为当您调用requestAnimFrame 时会立即调用animloop。不是这样! requestAnimFrame 是异步的。因此,语句按照您看到的顺序执行。这意味着主线程不等待requestAnimFrame 的调用返回,render() 的调用之前。所以render() 几乎立即被调用。然而,回调(在本例中为 animloop不会 立即调用。当您已经退​​出对animloop第一次 调用时,它可能会在将来的某个时间被调用。这个对animloop 的新调用有它自己的上下文和堆栈,因为它实际上并没有从第一个animloop 调用的执行上下文调用。这就是为什么您不会以无限递归和堆栈溢出而告终。

【讨论】:

好的,现在这更有意义了,我对使用回调的理解仍然不稳定,所以我必须做一些研究。谢谢:) 这是一个非常简洁的答案,消除了我对同一问题的困惑。我会说这应该是公认的答案:)

以上是关于为啥在循环开始时调用 requestAnimationFrame 不会导致无限递归?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在我使用导入的 .lib 运行项目时调用“用户断点”,而不是在代码内联时调用?

为啥在初始化程序中设置时调用 didSet?

为啥在通过 const 引用传递临时值时调用复制构造函数?

为啥在 std::for_each() 返回时调用转换运算符?

为啥 Promise 构造函数需要一个在完成时调用 'resolve' 的函数,但 'then' 不需要 - 它返回一个值?

在Node js中完成for循环时调用函数