58

I'm having some trouble with plain old JavaScript (no frameworks) in referencing my object in a callback function.

function foo(id) {
    this.dom = document.getElementById(id);
    this.bar = 5;
    var self = this;
    this.dom.addEventListener("click", self.onclick, false);
}

foo.prototype = {
    onclick : function() {
        this.bar = 7;
    }
};

Now when I create a new object (after the DOM has loaded, with a span#test)

var x = new foo('test');

The 'this' inside the onclick function points to the span#test and not the foo object.

How do I get a reference to my foo object inside the onclick function?

Chris MacDonald
  • 5,975
  • 4
  • 34
  • 35
  • **Save reference**: `this.cb = this.onclick.bind(this); addEventListener("click", this.cb);` – vsync Aug 10 '20 at 10:48

7 Answers7

81

(extracted some explanation that was hidden in comments in other answer)

The problem lies in the following line:

this.dom.addEventListener("click", self.onclick, false);

Here, you pass a function object to be used as callback. When the event trigger, the function is called but now it has no association with any object (this).

The problem can be solved by wrapping the function (with it's object reference) in a closure as follows:

this.dom.addEventListener(
  "click",
  function(event) {self.onclick(event)},
  false);

Since the variable self was assigned this when the closure was created, the closure function will remember the value of the self variable when it's called at a later time.

An alternative way to solve this is to make an utility function (and avoid using variables for binding this):

function bind(scope, fn) {
    return function () {
        fn.apply(scope, arguments);
    };
}

The updated code would then look like:

this.dom.addEventListener("click", bind(this, this.onclick), false);

Function.prototype.bind is part of ECMAScript 5 and provides the same functionality. So you can do:

this.dom.addEventListener("click", this.onclick.bind(this), false);

For browsers which do not support ES5 yet, MDN provides the following shim:

if (!Function.prototype.bind) {  
  Function.prototype.bind = function (oThis) {  
    if (typeof this !== "function") {  
      // closest thing possible to the ECMAScript 5 internal IsCallable function  
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");  
    }  

    var aArgs = Array.prototype.slice.call(arguments, 1),   
        fToBind = this,   
        fNOP = function () {},  
        fBound = function () {  
          return fToBind.apply(this instanceof fNOP  
                                 ? this  
                                 : oThis || window,  
                               aArgs.concat(Array.prototype.slice.call(arguments)));  
        };  

    fNOP.prototype = this.prototype;  
    fBound.prototype = new fNOP();  

    return fBound;  
  };  
} 
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
hishadow
  • 1,145
  • 7
  • 9
  • closure() is probably a better name though. – hishadow Aug 16 '09 at 18:48
  • The closure won't work when you're using several objects; you'll always be referring to the last object created. The utility function works because the argument is newly stored every time. – w00t Jan 27 '12 at 00:00
15
this.dom.addEventListener("click", function(event) {
    self.onclick(event)
}, false);
Yi Jiang
  • 49,435
  • 16
  • 136
  • 136
Sergey Ilinsky
  • 31,255
  • 9
  • 54
  • 56
  • 1
    Thanks, but can you explain why I need to create an anonymous function there? Why does that change the binding? – Chris MacDonald Oct 08 '08 at 15:02
  • Whenever an event is fired, 'this' refers to the object that invoked the event. – Tom Oct 08 '08 at 15:04
  • 2
    When you use the form in your question, self.onclick is just an anonymous function which, when handled, works like you had attached it directly to the span. When you wrap it in a closure, self.onclick(event) really does what you want, e.g. uses 'self' from the context defining the closure. – Adam Bellaire Oct 08 '08 at 15:07
  • This will work only for one object being created; consider the output of ```function () {var i; for (i=0; i<5; i++) { setTimeout( function() { console.log(i); }, 1000); } }; f();``` – w00t Jan 27 '12 at 00:00
  • I take that back - there's a new ``var self`` being created on each invocation and it doesn't change unlike the example in my previous comment. (there's a typo in it too, should start with ``function f()``) – w00t Feb 10 '12 at 09:43
5

For the jQuery users looking for a solution to this problem, you should use jQuery.proxy

Seldaek
  • 40,986
  • 9
  • 97
  • 77
2

A good explanation of the problem (I had problems understanding solutions described so far) is available here.

tpk
  • 2,041
  • 5
  • 21
  • 29
2

The explanation is that self.onclick does not mean what you think it means in JavaScript. It actually means the onclick function in the prototype of the object self (without in any way referencing self itself).

JavaScript only has functions and no delegates like C#, so it is not possible to pass a method AND the object it should be applied to as a callback.

The only way to call a method in a callback is to call it yourself inside a callback function. Because JavaScript functions are closures, they are able to access the variables declared in the scope they were created in.

var obj = ...;
function callback(){ return obj.method() };
something.bind(callback);
Vincent Robert
  • 35,564
  • 14
  • 82
  • 119
  • I called the prototype function 'onclick' to easily associate it. I knew it wasn't going to be called automatically by the actual DOM onclick event, which was why I was trying to do the event listeners to bind my object's onclick with the DOM's onclick function. – Chris MacDonald Oct 11 '08 at 13:23
  • That was not my point, I understand your onclick function. My point is that there is no difference in JavaScript between self.onclick and foo.prototype.onclick. There is no way in JavaScript to say "this method bound to this object". – Vincent Robert Oct 12 '08 at 15:55
  • The only way is to use a closure. – dolmen Aug 23 '10 at 09:39
1

I wrote this plugin...

i think it will be useful

jquery.callback

-3

this is one of the most confusing points of JS: the 'this' variable means to the most local object... but functions are also objects, so 'this' points there. There are other subtle points, but i don't remember them all.

I usually avoid using 'this', just define a local 'me' variable and use that instead.

Javier
  • 60,510
  • 8
  • 78
  • 126
  • 2
    Your statement is completely inaccurate. Both a function's apply() and call() methods allow you to execute a function with 'this' referring to a variable you pass along in the call. – Kenaniah Feb 02 '10 at 19:22
  • 1
    @Kenaniah:.... thus adding to the confusion, the exact meaning of the code depends on how it's called! – Javier Feb 02 '10 at 20:46
  • Functions are very flexible in JavaScript and not using `this` is hardly a solution to the problem. `this` is the only dynamic scoping feature in JS and it adds a lot of power to the language. And yes, the fact that functions are first-class objects in JS implies the dynamic nature of the 'execution context pointer'. It's a great feature when used wisely. – Arman Jun 04 '13 at 09:32