0

Can someone explain me why the code below doesn't print the numbers from 0 to 2 on this.closure unless I do new crazyClosure(i) or crazyClosure.call({}, i)?

var crazyClosure = function (count) {
    var self = this;

    function closure () {
        console.log("closure", count);
    };

    this.closure = function () {
        console.log("this.closure", count);
    };

    console.log("init", count);
    setTimeout(function () {
        console.log('');
        console.log("call", count);
        closure();
        self.closure();
    }, 0);
}

for (var i = 0; i < 3; i++) {
    crazyClosure(i);
}

Is like if the closure would be attached to this instead of to the crazyClosure function itself.

I would expect to see:

init 0
init 1
init 2
call 0
closure 0
this.closure 0
call 1
closure 1
this.closure 1
call 2
closure 2
this.closure 2

but I get

init 0
init 1
init 2
call 0
closure 0
this.closure 2
call 1
closure 1
this.closure 2
call 2
closure 2
this.closure 2

Adding another example according to answers. In this example I have 2 functions on this which is window that get overridden every time I call crazyClosure though I get the result I was expecting when calling expected but I get the result you guys say I should get when calling unexpected. According to your explanation I still don't understand how that can happen.

function crazyClosure (count) {
    var self = this;

    this.expected = function () {
        console.log("this.expected", count);
        self.unexpected();
    };

    this.unexpected = function () {
        console.log("this.unexpected", count);
    };

    console.log("init", count);
    setTimeout(self.expected, 10);
}

for (var i = 0; i < 3; i++) {
    crazyClosure(i);
}

I get:

init 0
init 1
init 2
this.expected 0
this.unexpected 2
this.expected 1
this.unexpected 2
this.expected 2
this.unexpected 2

I need to understand this!!! Thanks

Fede
  • 53
  • 1
  • 7
  • In your code, `this` refers to the global object, which in web browsers is `window`. So what you're doing is creating ONE global called `closure` which is referenced via `this.closure` and you keep replacing THE SAME variable with a new function. Hence, when setTimeout executes it logically executes the last function assigned, three times. If you call the function with `new` instead a new empty object will be created (inheriting from the function's prototype) and assigned to `this`. Either way, this has nothing to do with closures but rather the concept of `this` in javascript. – slebetman Oct 03 '13 at 07:00
  • See my answer to this other question for a full description of how `this` works in javascript: http://stackoverflow.com/questions/13441307/how-does-the-this-keyword-in-javascript-act-within-an-object-literal/13441628#13441628 – slebetman Oct 03 '13 at 07:03
  • To help understand the interpretation of `this` in functions, try reading John Resig's excellent article [Simple “Class” Instantiation](http://ejohn.org/blog/simple-class-instantiation/#postcomment). Unless you are a total genius, you won't understand every word first time - I have read it a dozen times and still need to go back to it on occasions. It provides enormous insight. – Beetroot-Beetroot Oct 03 '13 at 16:16

2 Answers2

3

You should try to separate the concept of closure from the concept of Javascript object.

They are totally unrelated and mixing them while learning can bring in more confusion than needed.

In your code the problem is that crazyClosure seems like a constructor, but is called as a function so it will not receive a this object to work on and thus will work with this that is set to window.

The result is that self is also going to be tied to window and the call self.closure() will invoke last closure your loop created. All three timeout events will invoke last closure because Javascript will not execute a timeout until your loop terminates.

Please also do yourself a favour and leave immediately the "Javascript closures are broken" attitude. If your first thought is that other people's code is broken you are not going to go any far in programming. Please don't take it personally, and don't think it's a rant about how politely you ask a question. The mental habit of thinking that your code is correct and other's people code (a library, the compiler, the OS) is wrong is a true killer... don't fall into that trap.

Edit

The second example is more convoluted but still the resulting output is what a Javascript implementation should do.

One each call as said before this will be window because the new operator was not used when calling crazyClosure.

The two "methods" expected and unexpected will end up being global functions and you can check this because after the execution you will be able to call expected() directly from outside crazyClosure.

However while inside crazyClosure the value passed to setTimeout will be different each time because the global expected will have been just overwritten with a new closure that can access the local count variable. This expected global variable will be overwritten later, but when the value has been already passed to setTimeout and the Javascript runtime has stored it away to call it 10 milliseconds later.

All those three closures however in their code call self.unexpected, and when this happens this global has been already overwritten three times, so all the three different closures "expected" will call the same closure "unexpected" (the last one) because when the timeout triggers it's what is currently stored in window.unexpected.

6502
  • 112,025
  • 15
  • 165
  • 265
  • 1
    +1 for the last paragraph. Your last sentence should be on SO's [How To Ask](http://stackoverflow.com/questions/how-to-ask) a question page. – nnnnnn Oct 03 '13 at 06:32
  • @nnnnnn: My remark was not about how to ask politely. The mental habit of thinking that your code is correct and bugs are in other people's code (being it a library, the language, the compiler, the OS) is really a big obstacle in progressing, even when you are working alone. It's not about being polite, it's about being able to grow your own programming abilities. – 6502 Oct 03 '13 at 06:42
  • Yes, I know what you meant, that's why I upvoted. I didn't mention politeness. The "How To Ask" page isn't about asking politely, it's a prompt to try to get people to ask sensible, answerable questions, and to not ask at all unless a thorough search has failed. So what I meant in my previous comment is that it would be nice if your point could be made to everybody thinking of asking a question here _before_ they post "Why is [popular library or language] broken?" – nnnnnn Oct 03 '13 at 12:32
  • Ok thanks for your answer. At school I learnt that a good title is what it makes people read the article. If you read that title again I'm just asking if its a bug I'm not saying it is. – Fede Oct 03 '13 at 21:13
  • @Fede: Sorry if you found this offensive, it wasn't meant to be. The fact that you even wonder if there is a bug in Javascript closures is a problem that will slow you down in the future. This should really be the LAST of your thoughts. – 6502 Oct 04 '13 at 00:10
  • @6502 can you have a look at my last edit. I've added a similar example that still doesn't make sense to me. – Fede Oct 04 '13 at 01:28
  • @6502 Thanks that kind of makes sense even though I don't find it intuitive at all. I get that the functions get overwritten because they are attached to the same object, its just that in my head I had the idea that the closure gets passed to a function when it gets called, but is like it gets passed when is created. So by overwriting the function you are actually overwriting the closure it will work with too. – Fede Oct 07 '13 at 03:21
1

If you don't use new, this at the start of the crazyClosure function is the enclosing context, not a new object. So all the calls end calling the closure function on the same self (which is window if your code isn't embedded).

Each call to

this.closure = function () {
    console.log("this.closure", count);
};

is equivalent to

window.closure = function () {
    console.log("this.closure", count);
};

and thus replaces the precedent function. You end up with only one window.closure function, embedding the last value, and that's this unique function you call three times with setTimeout.

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • But count is not assigned to this, it belongs to crazyClosure, I would expect to see `closure` and `this.closure` to print the same value. – Fede Oct 03 '13 at 05:51
  • You've put a lot of redundant code so it's hard to see what you expected. Can you write what log you expected ? – Denys Séguret Oct 03 '13 at 05:56
  • Thanks, got that. How that explanation fits with the last example I just added? What am I missing? – Fede Oct 04 '13 at 01:30