在setTimeout中使用JavaScript闭包

11

我正在使用setTimeout来模拟渲染过程,我遇到了这样的结构:

var Renderer = new Class (
{
    Implements: Events,

    initialize()
    {
        this.onRender();
    },

    onRender: function()
    {
        // some rendering actions
        setTimeout(this.onRender.bind(this), 20);
    }
});

由于闭包的无限嵌套,这段代码是否存在潜在内存泄漏问题?还是一切正常?到目前为止,我唯一想到的解决方案是将其重写为普通函数。

function Renderer()
{
    var onRender = function()
    {
        // rendering
        setTimeout(onRender, 20);
    };
    onRender();
};

但我不想失去Mootools事件和类。由于某些原因,我也不能使用“单例”(例如window.renderer = new Renderer();)


无限嵌套在哪里?您的问题/关注点不清楚。 - Ates Goral
没问题。顺便说一下,在mootools中,你只需要执行this.onRender.delay(20, this);就可以了。你的代码有问题,缺少initialize函数,但我猜你是拿它作为一个例子。 - Dimitar Christoff
1个回答

23
你的代码没问题,但是Andy的回答有误导性,因为它混淆了作用域链执行上下文,并且扩展到了调用栈
首先,setTimeout函数不会在全局作用域中执行。它们仍然在闭包中执行,并且可以访问外部作用域的变量。这是因为JavaScript使用静态作用域;也就是说,一个函数的作用域链创建该函数时就已经定义好了,并且永远不会改变;作用域链是函数的属性。 执行上下文作用域链不同且独立,因为它是在函数被调用时构建的(无论是直接调用 - func(); - 还是作为浏览器调用的结果,例如超时到期)。执行上下文由激活对象(函数的参数和局部变量)、对作用域链的引用以及this的值组成。 < p> 调用栈可以被视为执行上下文的数组。 栈底是全局执行上下文。 每次调用函数时,它的参数和this值都存储在堆栈上的新“对象”中。

如果我们将您的onRender函数更改为简单地调用自身(this.onRender()),则堆栈将很快溢出。 这是因为控制永远不会离开每个连续的onRender函数,从而允许其执行上下文从调用堆栈中弹出。 相反,我们越来越深入每个onRender,等待下一个onRender返回,在无限循环中打破,只有当堆栈溢出时才会发生。

但是,通过调用setTimeout,控制立即返回,因此能够离开onRender函数,导致其执行上下文从堆栈中弹出并被丢弃(由GC释放内存)。

当超时到期时,浏览器会从全局执行上下文启动对onRender的调用; 调用堆栈仅深度为二。 有一个新的执行上下文-默认情况下将继承全局范围作为其this值; 这就是为什么您必须bind到您的Renderer对象-但它仍然包括在您首次定义onRender时创建的原始作用域链。

正如你所看到的,通过递归你不会创建无限闭包,因为闭包(作用域链)是在函数定义时创建的,而不是在函数调用时创建的。此外,在onRender返回后,你也不会创建无限执行上下文,因为它们会被丢弃。

我们可以通过测试来确保你没有泄漏内存。我让它运行了500,000次,并没有观察到任何泄漏内存。请注意,最大调用堆栈大小约为1,000 (因浏览器而异),因此绝对不会递归。


非常感谢,乔什。这正是我想要澄清的。我担心在使用闭包传递setTimeout回调时会否认onRender上下文以弹出堆栈。我进行了测试,没有注意到任何内存溢出。但我知道可以自由地使用闭包,因为我不习惯使用它们。 - Nanako

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