And Finally
And Finally

Reputation: 5714

Javascript closures - variables vs parameters

I'm trying to learn Javascript closures. I'm having trouble getting my head around the fact that when you create several closures in a loop, all closures save only the last state of a variable. With this example

var links = document.getElementsByTagName('a');

for (var x=0; x<links.length; x++) attachListener();

function attachListener() {
        links[x].addEventListener('click', function(){
            console.log(x);
        }, false);
};

When I have three links in my document, clicking on any link shows "3", I guess because x got incremented to 3 after the final run of the loop. I read in this excellent intro that if you run the outer function multiple times a new closure's created each time. So how come each closure doesn't save a different value for x each time I call the outer function?

When you pass x as a parameter to the outer function it does work as expected.

var links = document.getElementsByTagName('a');

for (x=0; x<links.length; x++) attachListener(x);

function attachListener(z) {
        links[z].addEventListener('click', function(){
            console.log(z);
        }, false);
};

Now you get 0 when you click the first link, 1 on the second etc.

Can anyone please explain why there is this difference?

Cheers

Upvotes: 12

Views: 5942

Answers (4)

user2314737
user2314737

Reputation: 29427

This is a common bug (also known as Javascript's infamous loop problem) caused by a misunderstanding of closures. Here is a very basic example:

var funcs = [];
for (var i = 0; i < 5; i++) {
funcs[i] = function() { return i; };
}
> funcs[0]();
5
> funcs[1]();
5

The main thing to understand here is that a closure closes over (or remembers, or "grabs") references to non-local (or free) variables. Now the emphasis is on references, i.e. a closure does not grab the values of the free variables but it grabs references to their names.

If a closure would remember the values of the free variables then we would have the (wrongly) expected behavior of funcs[0]() returning 0, etc. If a closure would remember the values of free variables then we could say that a closure takes a "snapshot" of those values in that particular moment in time.

But that's not what a closure does.

Closures remember references to free variables and not their values.

In the example, funcs[i] remembers a reference to i. When invoked, it looks up the value of the global variable i and that value is currently 5.

What really helped me to understand this was this lecture pp. 12--15 from which I used some excerpts together with the definition on Wikipedia.

Upvotes: 0

Matthias Benkard
Matthias Benkard

Reputation: 15769

Closures do not capture the value of a variable at the time of their creation but the variable itself. A variable closed over by multiple closures is shared. This is intentional, and it is a good way of doing encapsulation in JavaScript, e.g. like this:

var makeMutablePoint = function(x, y) {
  return {
    position: function() {
      return [x, y];
    },
    add: function(dx, dy) {
      x = x + dx;
      y = y + dy;
    }
  };
};

This is also the way closures work in most other languages (which is the primary reason Python is sometimes said not to have proper closures).

There is one aspect to this that is JavaScript-specific, though, which may trip you up at times (and actually seems to have done so in this case): Variables always have function scope in JavaScript. For example, in your first code snippet, there is only a single x variable, whereas one might expect the scope of x to be restricted to the loop body (with a new x for each iteration). This is a quirk in the language which is probably going to be improved upon in the future by the introduction of a let keyword with more fine-grained scoping rules.

Upvotes: 13

aroth
aroth

Reputation: 54854

I've been somewhat irritated by this very same behavior in the past. It seems like a bug in the implementation of closures, to me. A closure is supposed to include a snapshot of "the function's lexical environment (e.g., the set of available variables and their values) at the time when the closure was created" (source: Wikipedia; emphasis mine). Manifestly that is not quite what is happening in this example.

But it's easy enough to reason out what is happening behind the scenes. In your first example, there is only a single instance of the variable x, and when the closure is created the JavaScript runtime is storing in it a reference to x instead of a copy of the current value of x at the time the closure is created. So when the loop increments x, the "copy" in the closure appears to increment as well.

In the second case, you pass x as a parameter to the function, which copies the current value of x into a new variable when it gets passed to the attachListener() function. The value of this copy is never updated (i.e. it is decoupled from x, and you do not modify it inside of attachListener()), and thus the closure works as expected, because it stores a reference to the copy and not to the original x.

Upvotes: 3

Marcel Korpel
Marcel Korpel

Reputation: 21763

In your first example, x is part of the scope of the outer function that attaches event listeners and so is increased to 3.

(outer scope)
    x
attachListener
    (no local variables)

In the second example, z becomes part of the scope of attachListener, that is preserved within the closure.

(outer scope)
    x
attachListener
    z

Upvotes: 3

Related Questions