开发者

Using JavaScript closures in setTimeout

I'm using setTimeout to emulate rendering, and I came to the structure like this:

var Renderer = new Class (
{
    Implements开发者_Go百科: 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


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 of this.

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 successive onRender function, allowing its execution context to be popped off the call stack. Instead, we go deeper and deeper with each onRender waiting for the next onRender 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 the onRender 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 its this value; that's why you have to bind to your Renderer object – but it still includes the original scope chain that was created when you first defined onRender.

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜