首页 > 解决方案 > 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()但我自己无法找到任何解决方案。如果我不使用匿名函数,我会丢失执行上下文,但我需要它!

标签: javascript

解决方案


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 以在下次绘制时由浏览器绘制。


推荐阅读