JavaScript Callback Scope

53,987

Solution 1

(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;  
  };  
} 

Solution 2

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

Solution 3

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

Solution 4

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

Solution 5

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

Share:
53,987
Chris MacDonald
Author by

Chris MacDonald

Updated on July 09, 2022

Comments

  • Chris MacDonald
    Chris MacDonald almost 2 years

    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?