在 setTimeout 中使用 JavaScript 闭包
我使用 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();)
I'm using setTimeout to emulate rendering, and I came to the structure like this:
var Renderer = new Class (
{
Implements: Events,
initialize()
{
this.onRender();
},
onRender: function()
{
// some rendering actions
setTimeout(this.onRender.bind(this), 20);
}
});
Does that code have potential memory leaks because of infinite nesting of closures? Or everything is ok? The only solution I came so far is to rewrite it to usual
function Renderer()
{
var onRender = function()
{
// rendering
setTimeout(onRender, 20);
};
onRender();
};
But I don't want to lose Mootools Events and Classes.
For some reasons I can't use a "singleton" (like window.renderer = new Renderer();) too
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您的代码很好,但安迪的回答具有误导性,因为它混淆了 范围链与执行上下文,以及扩展调用堆栈。
首先,
setTimeout
函数不会在全局范围内执行。它们仍然在闭包中执行并且可以从外部作用域访问变量。这是因为 JavaScript 使用静态作用域;也就是说,函数的作用域链是在函数创建时定义的,并且永远不会改变;作用域链是函数的一个属性。执行上下文与作用域链不同且独立,因为它是在调用函数时构造的(无论是直接 -
func();
– 或作为浏览器调用的结果,例如超时到期)。执行上下文由激活对象(函数的参数和局部变量)、作用域链的引用以及this
的值组成。可以将调用堆栈视为执行上下文的数组。堆栈的底部是全局执行上下文。每次调用函数时,其参数和 this 值都会存储在堆栈上的新“对象”中。
如果我们将
onRender
函数更改为简单地调用自身 (this.onRender()
),堆栈将很快溢出。这是因为控制永远不会离开每个连续的 onRender 函数,从而允许其执行上下文从调用堆栈中弹出。相反,我们越来越深入地让每个onRender
等待下一个onRender
返回,这是一个只有在堆栈溢出时才会中断的无限循环。但是,通过调用
setTimeout
,控制会立即返回,从而能够离开onRender
函数,导致其执行上下文从堆栈中弹出并被丢弃(由 GC 从内存中释放)。当超时到期时,浏览器从全局执行上下文发起对
onRender
的调用;调用堆栈只有两层深。有一个新的执行上下文 - 默认情况下它将继承全局范围作为其this
值;这就是为什么您必须绑定
到您的Renderer
对象 - 但它仍然包含您第一次定义onRender
时创建的原始作用域链。正如您所看到的,您没有通过递归创建无限闭包,因为闭包(范围链)是在函数定义时创建的,而不是在函数调用时创建的。此外,您不会创建无限的执行上下文,因为它们在
onRender
返回后被丢弃。我们可以通过测试来确保您没有泄漏内存。我让它运行了 500,000 次,没有观察到任何内存泄漏。请注意,最大调用堆栈大小约为 1,000(因浏览器而异),因此它绝对不是递归的。
Your code is fine, but Andy's answer is misleading because it confuses the scope chain with execution context and, by extension, call stack.
First,
setTimeout
functions do not execute in the global scope. They still execute in a closure and can access variables from outer scopes. This is because JavaScript uses static scope; that is, the scope chain of a function is defined at the moment that function is created and never changes; the scope chain is a property of the function.Execution context is different and separate from the scope chain in that it is constructed at the time a function is invoked (whether directly –
func();
– or as the result of a browser invocation, such as a timeout expiring). The execution context is composed of the activation object (the function's parameters and local variables), a reference to the scope chain, and the value ofthis
.The call stack can be thought of as an array of execution contexts. At the bottom of the stack is the global execution context. Each time a function is called, its parameters and
this
value are stored in a new 'object' on the stack.If we were to change your
onRender
function to simply call itself (this.onRender()
), the stack would quickly overflow. This is because control would never leave each successiveonRender
function, allowing its execution context to be popped off the call stack. Instead, we go deeper and deeper with eachonRender
waiting for the nextonRender
to return, in an infinite cycle broken only when the stack overflows.However, with a call to
setTimeout
, control returns immediately and thus is able to leave theonRender
function, causing its execution context to be popped off the stack and discarded (to be freed from memory by the GC).When the timeout expires, the browser initiates a call to
onRender
from the global execution context; the call stack is only two deep. There is a new execution context – which by default would inherit the global scope as itsthis
value; that's why you have tobind
to yourRenderer
object – but it still includes the original scope chain that was created when you first definedonRender
.As you can see, you're not creating infinite closures by recursion because closures (scope chains) are created at function definition, not at function invocation. Furthermore, you're not creating infinite execution contexts because they are discarded after
onRender
returns.We can make sure you're not leaking memory by testing it. I let it run 500,000 times and didn't observe any leaking memory. Note that the maximum call stack size is around 1,000 (varies by browser), so it's definitely not recursing.