Evgenyt
Evgenyt

Reputation: 10741

JavaScript closure loses this reference?

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?

Upvotes: 2

Views: 389

Answers (5)

Šime Vidas
Šime Vidas

Reputation: 186103

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.

Upvotes: 1

Jim Jose
Jim Jose

Reputation: 1319

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.

Upvotes: 1

George
George

Reputation: 4257

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

Upvotes: 2

John Gibb
John Gibb

Reputation: 10763

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.

Upvotes: 3

HBP
HBP

Reputation: 16063

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);

Upvotes: -2

Related Questions