我正在使用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();)
-
无限嵌套在哪里? 您的问题/疑虑尚不清楚。
-
都很好。 顺便说一句,你可以在mootools中做this.onRender.delay(20, this);。 你的代码是错误的,它缺少初始化:function()但我想你把它作为一个例子。
你的代码很好,但是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的回调时,我会以某种方式拒绝onRender的上下文弹出堆栈。我运行测试,但没有注意到任何内存溢出。但我知道自由使用闭包因为我不习惯它。
setTimeout函数在全局范围内执行,它们不知道实例化它们的范围上下文。使用Javascript递归时,您需要注意的是递归太深,因为每次递归调用都会创建一个在内存中构建的新范围上下文。在这种情况下,setTimeout将其重置为全局范围,因此它在技术上不是递归。
编辑:这个答案是不正确的。看评论。
-
'setTimeout(this.onRender.bind(this),20);'在该行上它创建绑定函数并将其应用于它。在调用setTimeout之前,"父"函数的范围是否被销毁,或者它是否继续存在?
-
是的,你只是设置执行onRender时this将会是什么,但是它不知道之前的执行堆栈
-
但是如果我在onRender中声明一个新的局部变量并将其绑定为'this',那么"父"的范围将永远存在,不是吗?
-
虽然,没有渲染方法调用本身可能更好,并实现像render(){setInterval(drawFrame,10);}这样的函数。
-
问题是我需要暂停渲染,但我想我会使用setInterval,只是跳过"帧"。 thy Andy Ray。
-
只需存储间隔并停止/重新启动它。 setInterval返回唯一的id。 this.interval = setInterval(...); ... clearInterval(this.interval)然后根据需要启动一个新的
-
@Andy,你把你的概念混淆了;你的答案是不正确的。虽然在不同的执行上下文中调用setTimeout函数(意味着它具有不同的this值),但不会从新的作用域链调用它。由setTimeout调用的函数位于闭包中,可以从外部作用域访问变量。如果您将函数代码作为字符串 - setTimeout('doSomethingInGlobalScope();', 1);传递,那么setTimeout函数仅在全局范围内执行,这是您从未做过的,因为这与使用eval相同。
-
啊,该死的。 @Nanako如果你取消检查我的答案,我会删除它。
-
@AndyRay(迟到提醒)你想删除这个答案...... :-)