1

Today I ran into a very odd problem using Javascript's prototype and the this reference.

Basically, I have two objects. One object makes use of a div element and makes is possible to register an onclick event on the div. The other object makes use of this functionality and registers the onclick event using the first object.

Here is the code:

My first object, which takes care of the div:

DivObject = function() {};
DivObject.prototype.setClickListener = function(clickListener){
    document.getElementById("myDiv").onclick = function(e){
        clickListener(e);
    };
};

My second object uses this functionality:

MainObject = function(){
    this.myString = "Teststring";
    this.divObj = new DivObject();
    this.divObj.setClickListener(this.handleClick);
};

MainObject.prototype.handleClick = function(e){
    // do something with e
};

The problem is, that inside MainObject.prototype.handleClick, the this reference refers to the window object, not to the MainObject. So, console.log(this) inside this function logs [Object window].

You can see this behaviour on jsfiddle.

As a workaround, I use the setClickListener function as follows:

var thisRef = this;
this.divObj.setClickListener(function(e){
    thisRef.handleClick(e);
});

Now, the this reference in my handleClick function refers to my MainObject (as I want it to be).

See this workaround on jsfiddle.

My questions are:

Why does the this reference one time refer to the window object and one time to the this reference of my object? Is it overridden somewhere? I always thought using this in my object I can be sure that it is really the this reference of my object? Is the workaround I am using now the way how this problem should be solved or are there any other, better way to handle this case?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Uooo
  • 6,204
  • 8
  • 36
  • 63
  • 1
    How `this` works is described here: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/this. – Felix Kling Oct 03 '12 at 06:08
  • Because `this` is set by how you call the function, not how you create or assign it (except for ES5 bind). – RobG Oct 03 '12 at 06:26

2 Answers2

1

You could fix this by using .bind():

this.divObj.setClickListener(this.handleClick.bind(this));

See the demo.

xdazz
  • 158,678
  • 38
  • 247
  • 274
1

Your questions:

Why does the this reference one time refer to the window object and one time to the this reference of my object?

Other than using bind, the value of a function's this is set by how the function is called. When you do:

this.divObj.setClickListener(this.handleClick);

you are assigning a function reference, so the function is called later without any qualification (i.e. it's called as just handleClick rather than this.handleClick). On entering the function, because its this isn't set by the call, it will default to the global (window) object, or in strict mode remain undefined.

Is it overridden somewhere?

No, the value of this is set on entering an execution context. You can't overwrite it or directly assign to it, you can only set it in the call (e.g. as a method of an object, using new, apply, call) or using bind (also, arrow functions adopt the this of their enclosing lexical execution context).

I always thought using this in my object I can be sure that it is really the this reference of my object?

At the point you make the assignment, this is what you expect. But you are assigning a reference to a funciotn, not calling the function, so its this isn't set at that moment but later when it's called.

Is the workaround I am using now the way how this problem should be solved or are there any other, better way to handle this case?

Your work around is fine (and a common fix), it creates a closure so may have minor memory consequences but nothing serious. For very old versions of IE it would create a memory leak due to the circular reference involving a DOM object, but that's fixed.

The bind solution is probably better from a clarity and perhaps maintenance viewpoint. Remember to include a "monkey patch" for browsers that don't have built–in support for bind.

Please post code on SO, there is no guarantee that code posted elsewhere will continue to be accessible. The work around code:

MainObject = function(){
    this.myString = "Teststring";
    this.divObj = new DivObject();
    var thisRef = this;
    this.divObj.setClickListener(function(e){
        thisRef.handleClick(e);
    });
};
RobG
  • 142,382
  • 31
  • 172
  • 209