关于mootools:在setTimeout中使用JavaScript闭包

Using JavaScript closures in setTimeout

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var Renderer = new Class (
{
    Implements: Events,

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

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

由于闭包的无限嵌套,该代码是否存在潜在的内存泄漏? 或者一切都好吗? 我到目前为止唯一的解决方案是将其重写为常规

1
2
3
4
5
6
7
8
9
function Renderer()
{
    var onRender = function()
    {
        // rendering
        setTimeout(onRender, 20);
    };
    onRender();
};

但我不想失去Mootools活动和课程。
由于某些原因,我不能使用"singleton"(如window.renderer = new Renderer();)


你的代码很好,但是Andy的答案是误导性的,因为它将范围链与执行上下文混淆,并且通过扩展调用堆栈。

首先,setTimeout函数不在全局范围内执行。它们仍然在闭包中执行,并且可以从外部作用域访问变量。这是因为JavaScript使用静态范围;也就是说,函数的范围链是在创建函数时定义的,并且永远不会改变;范围链是函数的属性。

执行上下文与范围链是不同的,因为它是在调用函数时构造的(无论是直接– func();–还是作为浏览器调用的结果,例如超时到期)。执行上下文由激活对象(函数的参数和局部变量),对作用域链的引用以及this的值组成。

调用堆栈可以被认为是执行上下文的数组。堆栈的底部是全局执行上下文。每次调用函数时,其参数和this值都存储在堆栈中的新"对象"中。

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

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

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

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

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


setTimeout函数在全局范围内执行,它们不知道实例化它们的范围上下文。使用Javascript递归时,您需要注意的是递归太深,因为每次递归调用都会创建一个在内存中构建的新范围上下文。在这种情况下,setTimeout将其重置为全局范围,因此它在技术上不是递归。

编辑:这个答案是不正确的。看评论。