javascript - JavaScript中有没有办法减少递归函数调用中的调用堆栈大小?
问题描述
我有一个负责在画布上绘制的函数。该函数做了一些繁重的工作,据我了解,这个繁重的工作变量保存在内存中,因为 requestAnimationFrame() 不会清除/重置调用堆栈。
最近的 Firefox 更新向我提出了这个问题(导致在几秒钟内使用了 > 10GB 的 RAM)。它可以在以前版本的 Firefox 中运行,也可以在 Chrome 中运行,因此它可能是一个错误。但尽管如此,我想知道是否有办法自己解决这个问题或编写更好的代码。
提前致谢!
示例代码:
"use strict";
class DrawUnit {
constructor(canvas){
this.canvas = canvas;
window.requestAnimationFrame(()=>{
this.draw()
});
}
draw(){
let data = generateAndProcessData(); //heavy lifting happening here, old data stays in memory because the previously called draw() still has a reference to it right?
let ctx = this.canvas.getContext('2d');
//reset canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(let d of data){
//draw some line
}
window.requestAnimationFrame(()=>{
this.draw()
})
}
}
setTimeout() 提供相同的结果,但我更喜欢使用 requestAnimationFrame()
我认为问题和解决方案与第 5 行中的匿名函数有关,this.draw()
但我自己无法找到任何解决方案。如果我不使用匿名函数,我会丢失执行上下文,但我需要它!
解决方案
draw
实际上不是递归的。Firefox 的调用堆栈向您展示了一个名义调用堆栈,其中包括用于调试目的的异步嵌套。返回时清除实际调用堆栈draw
。
requestAnimationFrame
理论上,您创建的要传递给的闭包确实data
通过它关闭的词法环境对象间接保留了对的引用。浏览器可能会也可能不会优化该链接(我知道 V8,Chrome 中的 JavaScript 引擎和其他引擎,过去在闭包优化方面相当激进,但由于执行时间的影响而有所退缩 -并非完全 -它有过)。
好消息是我们可以通过使用完全避免关闭bind
:
requestAnimationFrame(this.draw.bind(this));
bind
创建一个新函数,该函数在调用时将调用原始函数,并将其this
设置为bind
的第一个参数。它不会在当前上下文中创建闭包。
所以现在在调用过程中没有创建任何闭包draw
,所以之前的调用没有保留任何内容data
(在没有优化的情况下)。
不过,您给出的回调requestAnimationFrame
不应该做繁重的工作。它需要非常快速地完成它的工作。(如果rAF
回调花费的时间超过几毫秒,浏览器会向您抱怨。)
如果“繁重的工作”在 CPU 时间方面很重要,您可能希望尽可能将其卸载到web worker并将最终数据发布到主线程 ( postMessage
) 以呈现到 DOM。处理该message
事件的主线程中的代码随后会将其渲染到 DOM 以在下次绘制时由浏览器绘制。
推荐阅读
- flutter - pageView.builder 中的容器大小没有变化
- html - 向左添加填充以流动 html:文本不移动
- javascript - 根据源对象属性有条件地分配类名
- kubernetes - StatefulSet breaking Kafka on worker reboot (unordered start)
- css - 无样式的内容出现在 Chrome 上
- rest - Rust + Rocket:我如何从请求中读取 POST 正文作为字符串?
- orientdb3.0 - 如何在 3.1.1 MultiModel API 中创建具有强制值的顶点
- npm - yarn 工作区的私有 npm 注册表有问题
- reactjs - Jest 无法解析 React 项目中 SASS 文件中使用的别名
- spring - Spring Boot @ConfigurationProperties 用于嵌套结构