views:

77

answers:

5

I have two Javascript "objects" similar to so....

var Object2 = new (function() {
    this.FetchData = function(callback) {
        // do some stuff
        callback(data);
    };
});

var Object1 = new (function() {

    this.DisplayStuff = function() {

    };

    this.LoadData = function() {
        Object2.FetchData(this.OnData);
    };

    this.OnData = function(data) {
        // this == window
        this.DisplayStuff();   // doesn't work
    };

});

When Object1 receives the callback to OnData, the value of "this" is set to window. Is there any way I can get around this so that the value of "this" inside of OnData will be the instance of Object1 instead of window?

A: 

The basic solution is to force context on the callback with apply. (Or call. The difference is how parameters are passed. Either an array or 'normally') See the MDC for documentation.

Alex Mcp
A: 

For a callback, you're stuck with 'this' being the window (usually). However, you could add a member to Object1 that points to itself (a lot of times it's gets call 'self', i.e. "var self = this;"), and then use that, i.e. self.DisplayStuff().

Anthony -GISCOE-
+3  A: 

The simple way of doing it is storing a reference to this in a variable, then using the call() method:

this.LoadData = function() {
    var self = this;
    Object2.FetchData(function () { self.OnData.call(self) });
};

If you're doing this a lot, you might want to consider using the .bind() method on the function prototype in ECMAScript 5th edition. This method can be implemented where unsupported:

// From Prototype.js
if (!Function.prototype.bind) { // check if native implementation available
  Function.prototype.bind = function(){ 
    var fn = this, args = Array.prototype.slice.call(arguments),
        object = args.shift(); 
    return function(){ 
      return fn.apply(object, 
        args.concat(Array.prototype.slice.call(arguments))); 
    }; 
  };
}

And the resulting function call:

this.LoadData = function() {
    Object2.FetchData(this.OnData.bind(this));
};

PrototypeJS - bind()

Andy E
Slick! Something like this is what I was hoping for.
T. Stone
+5  A: 

A technique commonly used in frameworks is to let the caller decide on what the this context should be for the callback function. So the FetchData function looks like (thanks to @Andy for telling me about the default scope when context is null or undefined - it is the global object),

this.FetchData = function(callback, context) {
    callback.call(context, data);
};

When calling FetchData, pass Object1 as the context for the callback,

Object2.FetchData(this.OnData, this);

Another option is to bind the OnData function with Object1 using Function.prototype.bind

Object2.FetchData(this.onData.bind(this));
Anurag
I wish I could pick two answers. This is a great response.
T. Stone
No need for `context || window`, if context is *undefined* or *null* it will already default to the global object, which is *window* in the case of webpages.
Andy E
@Andy - thanks for pointing our the default behavior for unspecified context. thanks @T. Stone - glad you liked it
Anurag
A: 

The ambiguity of "this" is really annoying to me with javascript. So I just avoid "this" as much as possible. I do it this way:

var Object2 = (function() { // no need for a "new" keyword any more

    var self = {}; // instead of the auto-supplied "this", create my own "self"

    self.FetchData = function(callback) {
        // do some stuff
        callback(data);
    };
    return self; // don't forget to return "self"
});

var Object1 = (function() {

    var self = {}; // instead of the auto-supplied "this", create my own "self"

    self.DisplayStuff = function() {

    };

    self.LoadData = function() {
        Object2.FetchData(this.OnData);
    };

    self.OnData = function(data) {
        self.DisplayStuff();
    };

    return self; // don't forget to return "self"

});

You lose the ability to use Object.prototype with this technique, you lose the ability to test using "instanceof", but you never have to think about what "this" is.

morgancodes
Why not just set self to this? Then you don't lose those abilities.
Anthony -GISCOE-
That works totally fine too. I think I starting doing it this way because I was feeling angry at "this" and was excited about the idea of banishing it.
morgancodes
Actually, that's not true. I've been experimenting with Crockford's "Parasitic Inheritance" approach, which eschews "this". http://blog.higher-order.net/2008/02/21/javascript-parasitic-inheritance-power-constructors-and-instanceof/
morgancodes