开发者

Recursive closures in JavaScript

Let's say I have something like开发者_开发百科

function animate(param)
{
    // ...
    if (param < 10)
        setTimeout(function () { animate(param + 1) }, 100);
}

animate(0);

Does this mean each instance of the function's local data will be held in memory until animate completes, i.e. until param reaches 10?

If it's true that instances are held in memory, is there a better way of doing this? I know, passing textual code to setTimeout() solves the problem but in my case there are objects among function arguments that can't be represented as strings easily.


No, at most two instances of function's local data will be held in memory at any given point in time. Here is the order of events:

  1. animate(0) is called.
  2. A closure with param == 0 is created, it now prevents this variable from being released.
  3. Timeout fires, animate(1) is called.
  4. New closure with param == 1 is created, it now prevent this variable from being released.
  5. The first closure finishes executing, at this point it is no longer referenced and can be released. The local variables from the first animate() call can also be released now.
  6. Repeat starting with step 3, now with animate(2).


Actually, you don't create a recursive function there. By invoking setTimeout its not calling itself anymore. The only closure created here is the anonymous function for setTimeout, once that is executed the garbage collector will recognize that the reference to the previous instance can get cleaned up. This probably will not happen instantly, but you definetly can't create a stack overflow using that. Lets view this example:

function myFunc() {
   var bigarray = new Array(10000).join('foobar');

   setTimeout(myFunc, 200);
}
myFunc();

Watching the memory usage from your browser now. It'll grow constantly, but after a while (20-40 seconds for me) it'll get completely cleaned again. On the other hand, if we create a real recursion like this:

function myFunc() {
   var bigarray = new Array(10000).join('foobar');

   myFunc();
}
myFunc();

Our memory usage will grow, browsers locks down and we finally create that stack overflow. Javascript does not implement Tail recursion, so we will end up with an overflow in all cases.


update

it lookes like I was wrong in my first example. That behavior is only true if no function-context is invoked (using a anonymous function for instance). If we re-write that like

function myFunc() {
   var bigarray = new Array(10000).join('foobar');

   setTimeout(function() {
     myFunc();
   }, 200);
}
myFunc();

Browser memory seems not to get released any more. Grows forever. That might be because any inner-closured keeps a reference to bigarray. But anyway.


Only the context related to the most recent call to animate will be retained (in theory, it's all down to the garbage collector). When animate creates the anonymous function, the anonymous function gets a reference to the context of that call to animate and so that context remains in memory. When the timeout occurs, the timer code's reference to the anonymous function is released, which releases that function's reference to the context. A new context has meanwhile been created, but the new context doesn't reference the old one, so it won't keep the old one in memory.

The key to this is that the binding of the context to the function happens when the function is created, not when it's called.


Yes, in your example, each time the animate function is executed a new function is created with its own closure scope.

To avoid the multiple closures, you could try something like this:

function animate (param) {
    function doIt () {
        param++;
        if (param < 10) {
            setTimeout(doIt, 100);
        }
    };
    setTimeout(doIt, 100);
}


How about

function animate(param)
{
  //....
  if(param < 10)
    animateTimeout(param);
}

function animateTimeout(param)
{
   setTimout(function() { animate(param + 1) }, 100 );
}

This way you aren't storing the local data in //... while waiting for the timeout.

Not sure if you're thinking there is more of a problem here than there really is however. Your code isn't recursive in that it will result in 10 deep chain of closures because closure 1 is exited once the second call is made to animate. Each closure only exists for the lifetime of one setTimeout.

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜