0

I'm creating an array of callbacks like this:

function createFunctions(n) {
  var callbacks = [];

  for (var i=0; i<n; i++) {
    callbacks.push(function() {
      return i;
    });
  }

  return callbacks;
}

And, here's the test that I'm working with:

var callbacks = createFunctions(5);

for (var i=0; i<callbacks.length; i++) {
  Test.assertEquals(callbacks[i](), i, 'Function with index ' + i);
}

Basically, I want the callback to return it's index in the array (i.e. callback[1]() should return 1). But, i defined in createFunctions is set to 6 when the test runs so they always return 6. I've tried creating a local variable to hold the value in the anonymous function but that's not working either.

Any ideas how to make this work?

spinlock
  • 3,737
  • 4
  • 35
  • 46

1 Answers1

2

A closure has an enduring reference to the variables it closes over, not a copy of their value as of when it was created. That's why all of your functions see 6: That's the value that i has as of when those functions are called.

If you need them to see different values, make the closures close over something else that doesn't change. Here's one way:

function createFunctions(n) {
    var callbacks = [];

    for (var i=0; i<n; i++) {
      callbacks.push(makeCallback(i));
    }

    return callbacks;

    function makeCallback(index) {
        return function() {
          return index;
        };
    }
}

Or:

function createFunctions(n) {
    var callbacks = [];

    for (var i=0; i<n; i++) {
      callbacks.push(makeCallback(i));
    }

    return callbacks;

}

function makeCallback(index) {
    return function() {
      return index;
    };
}

(In this example, it doesn't matter whether makeCallback is within createFunctions or outside it.)

Now, the callbacks close over the context containing the index argument. They each get their own copy of that context (which is created by calling the makeCallback function), and so since nothing ever changes index, its value remains unchanged.

More (on my blog): Closures are not complicated

This feature of closures closing over the context of the variable is very, very useful. Consider:

function allocator(start) {
    return function() {
        return ++start;
    };
}
var f = allocator(0);
alert(f()); // "1"
alert(f()); // "2"
alert(f()); // "3"

That wouldn't work if the function created by allocator had a copy of the value of start. It works because the function created by allocator has a reference (through the context) to the variable itself.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • So an anonymous function within a function is not able to create a closure? – Kevin Bowersox Jan 17 '14 at 22:08
  • @KevinBowersox: Of course it creates (or rather, *is*) a closure. Did I suggest it doesn't? (Genuine question; you're a smart person, I may have misphrased something.) – T.J. Crowder Jan 17 '14 at 22:10
  • 1
    My question may not have been asked properly. If I am trying to create a closure that captures a variable, can I form that closure within the function that declares the variable or must I form the closure in a function outside of the function containing the variable. – Kevin Bowersox Jan 17 '14 at 22:12
  • @KevinBowersox: Ah, I see what you're asking. You can do it within that function. It doesn't matter where the closure is, what matters is what part of what its closes over it uses. So `makeCallback` can be within `createFunctions` or outside it, provided the only part it uses of the context it closes over is the `index` argument. If it needed some other aspect of the context of the call to `createFunctions`, it would need to be inside `createFunctions`. In this *particular* example, it doesn't, but it's common that it would. – T.J. Crowder Jan 17 '14 at 22:18
  • @KevinBowersox: When I say "it doesn't matter" I'm being a bit loose. In general, create closures at the outermost scope they *can* be created in; e.g., close over as little as possible. That minimizes creating functions unnecessarily. So in the above, we don't need it within `createFunctions`. That said, with modern engines, do what seems most readable and maintainable, performance is a distant concern compared to readability and maintainability. – T.J. Crowder Jan 17 '14 at 22:23
  • Thanks T.J. Crowder makes sense and helps. I always have to brush up on closures. I think the point I was trying to solidify was that the variable or object you want to capture must be passed to another function as an argument or the closure doesn't form (I think because it doesn't form a new scope or execution context). It is not simply returning a function from a function, like this: http://jsfiddle.net/BrK3j/1/ – Kevin Bowersox Jan 17 '14 at 22:28
  • @KevinBowersox: Giving away the ending of my blog post linked above: **All** JavaScript functions are closures. :-) So there's never a situation where you're creating a new function without forming a closure. It's possible to create one and not *use* it effectively. Closures are like [Matryoshka](http://en.wikipedia.org/wiki/Matryoshka_doll), Russian nesting dolls. Or onions. If you use something from an outer layer (as in your fiddle), you may not be getting the benefit. – T.J. Crowder Jan 17 '14 at 22:35
  • Crowder Thanks for the link to your blog (bookmarked) and the insights. Always appreciate it! – Kevin Bowersox Jan 17 '14 at 22:37
  • @KevinBowersox: And it's always a pleasure, with someone like yourself! :-) – T.J. Crowder Jan 17 '14 at 22:42