2

Consider the following code:

<script>
    var i = 0;
    function test() {
        var _this = this;
        // foo and _this.foo are set to the same number
        var foo = _this.foo = i++;

        function wtf() {
            console.log(_this.foo, foo);
        }

        $("#thediv").click(wtf);
    };
    test();
    test();
    test();
</script>

It seems that console.log(_this.foo, foo) should always output equal numbers (i).

But clicking the div outputs 3 lines (for each console.log call):

2 0
2 1
2 2

It seems that _this.foo always refers to last this.foo. Why it is so?

KevinDTimm
  • 14,226
  • 3
  • 42
  • 60
Evgenyt
  • 10,201
  • 12
  • 40
  • 44

5 Answers5

3

In your function wtf, the value captured in the variable foo is a scalar - the actual numeric value. Since it's capturing the scalar value at the moment the function is created, that function is closing over the value 0, 1, 2, etc. That's why it seems to increment.

However, when _this is captured, it's captured as a reference to a single instance which has a field named foo. Therefore, with _this.foo, you are referring to a field on the same instance.

So to sum it up, value types like integers are captured by their value, whereas an object is captured by it's reference.

John Gibb
  • 10,603
  • 2
  • 37
  • 48
  • This is the right answer. Another way to look at it is the foo variable is created each time you call the test method. Whereas the _this.foo is always referring to the same variable. The this.foo is essentially window.foo, so you each time you call the test method the window.foo variable is set to the new value. When you call the wtf method the window.foo value is going to be the last value assigned to it. – Phil McCullick Dec 09 '11 at 19:42
  • Great explanation... it's a lot easier to understand after coming from a C# background which has explicit value vs. reference types... – John Gibb Dec 09 '11 at 19:44
2

When test() is run, this is a reference to window for each of your three test() calls, so you are actually updating window.foo when you assign to _this.foo and referencing window.foo in your console.log.

Then, when wtf() is invoked, the _this variables in each of the wtf() closures are the same - they all point to window

See this jsfiddle: http://jsfiddle.net/8cyHm/

I added some additional parameters to console.log to show what is happening

George
  • 4,147
  • 24
  • 33
  • Why downvoted? He's right.. I didn't explain that `this` is actually `window` (that's accurate but irrelevant), but he explained it well and gave an example! – John Gibb Dec 09 '11 at 19:42
1

Its a tricky one, :)

The first and foremost thing that you have to understand is, you are calling the test function without a new prefix, which makes the this pointer inside the function refer to the window object

<script>
    var i = 0;
    function test() {
        var _this = this; //** this referes to the window object
        var foo = _this.foo = i++; //** incrementing the global var and assigning that to a local var and a window.foo var

        function wtf() {
            console.log(_this.foo, foo); //** reads window.foo and its local var foo
        }

        $("#thediv").click(wtf); //** creates a new lister every time the test function gets called
    };
    //** calling without the new keyword
    test(); //** creates foo-1, and wft-1 in memory, assigns foo-1=0; window.foo=0
    test(); //** creates foo-2, and wft-2 in memory, assigns foo-2=1; window.foo=1
    test(); //** creates foo-3, and wft-3 in memory, assigns foo-3=2; window.foo=2
</script>

(plz read the commented part inside the code)

Now, when you click on the div, which practically has 3 functions listening to its click event (3 inline wtf functions inside the test function)(each call to the test function creates a new wtf inline function). Each of these inline functions reads its on local var foo each of then have values 1,2,3 respectively.

Jim Jose
  • 1,319
  • 11
  • 17
1

So, this is how it works:

The test function is invoked three times. Each time a different wtf function object is created and bound to the DIV as its click handler. This means that after your above code is executed, there will be three click handlers bound to the DIV. When you then click on the DIV, those three handlers are invoked in sequence.

This line

var _this = this;

merely stores the global object into the local variable _this. If the function test were to be invoked in an strict-mode environment, this would be undefined and the code would throw an error. However, since it's not strict mode, the this value refers to the global object.

Btw, the i variable is declared in global code which makes it a global variable (global property).

This line

var foo = _this.foo = i++;

assigns the current value of i both to the local variable foo and to _this.foo. And since _this is a reference to the global object, the property foo is a global property (just like i).

Now, since we invoked the test function three times, there are also three separate foo local variables. Each of these variables captures the value of the i variable in the moment the test function was invoked. So, the values of those three foo variables are 0, 1, and 2. Those variables are captured by the three wtf functions (respectively) through closure. (The first wtf function captures the first foo variable, and so on.)

However, unlike the foo variables, there is only one foo global property. So, after each invocation of the test function, the foo global property is incremented. As a result, after test has been invoked three times, the value of _this.foo is 2. (This is before the DIV was clicked.)

Now, when the DIV is clicked, the three wtf functions will execute. Each of those functions will print the value of _this.foo which is 2. However, each of those wtf functions captured a different foo variable through closure. The values of those three foo variables are 0, 1, and 2, respectively.

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
-2

If you are testing in Chrome you may well be hitting a bug in console.log. See : Javascript Funky array mishap

Try changing to :

console.log(_this.foo + ' = ' + foo);
Community
  • 1
  • 1
HBP
  • 15,685
  • 6
  • 28
  • 34