views:

61

answers:

4

I'm attempting to make a class in javascript. I create it with the JSON type object thing.

Doing this:

Foo = {
   PubId: '',

   Init:function( oCallback )
   {
         this.sendCommand( 'INIT', {}, oCallback );
   },

    sendCommand : function( sCommand, aParams, oCallback )
    {

        setTimeout( oCallback, 1000, '{"response":"INIT","time":1287982024,"pubid":"4cc50bc47c7b3"}' );

        return true;
    },
    onData : function( sData )
    {
        var aRes = JSON.parse( sData );

        this.PubId = aRes.pubid;
        alert( this.PubId );
        return this.PubId;
    },
    umtest:function(){ alert( this.PubId ); }
}

I then also do this after including the script:

Foo.Init( Foo.onData ); 

The problem is that the this.PubId is updated inside the onData method, but outside of it, the pubid is empty.

I am pretty new at javascript classes, so I'm not sure what needs to be done so I was hoping someone could help meh out. :)

Thanks for your time!

A: 

The onData is executed separat from the class.

You have to call like this

Foo.Init(function(d) { Foo.onData(d);} ); 

for this to work as excpected.

David Mårtensson
this is an *anonymous function*, not a *closure*.
galambalazs
A: 

It is a scope problem. When you pass Foo.onData to your init function, it "looses" the connection to the class. Functions are first class objects, so they can exist by themselves. You could do the following:

Foo = {
   PubId: '',

   Init:function(oCallback, scope)
   {
         this.sendCommand('INIT', {}, oCallback, scope);
   },

    sendCommand : function(sCommand, aParams, oCallback, scope)
    {
        var cb = oCallback;
        if(scope) {
            cb = function(data) {
               oCallback.call(scope, data); 
            };
        }

        setTimeout( cb, 1000, '{"response":"INIT","time":1287982024,"pubid":"4cc50bc47c7b3"}' );

        return true;
    }
    //...
}

and then call it with

Foo.Init(Foo.onData, Foo);

The first parameter that is passed to call() will become this inside the method.

Felix Kling
Aha, thanks for the suggestion! :D
dab
+2  A: 

Well you've got two problems here. The first problem is not understanding how this works in Javascript. When Foo.onData is called via setTimeout( oCallback, ...) the this will reference to the global object not Foo.

In order to call it with Foo as this you should change your code:

sendCommand: function (sCommand, aParams, oCallback) {
    var that = this;           // save this reference
    setTimeout(function () {
        oCallback.call( that,  // call the function with it
               '{"response":"INIT","time":1287982024,"pubid":"4cc50bc47c7b3"}' );
    }, 1000);
    return true;
},

In order to test what's changed place this code to onData:

// is `this` really Foo or the global object?
alert(this === Foo);    // should be true
alert(this === window); // should be false

In the updated version this will correctly reference Foo as it's object of invocation.

The second problem you might be facing with is that your function called with setTimeout will only be executed after 1000 ms = 1s, so if you're simply checking alert(Foo.PubId) outside of Foo you will get an empty string (because the callback hasn't been called yet).

In order to test if Foo.PubId is indeed changed:

// execute the check after 2s so as to
// make sure the callback has been called
setTimeout( function () {
  alert(Foo.PubId);
}, 2000);

You can check the full test case here.

galambalazs
Thanks for the help! And same goes for everyone else. lol wish I could up vote all of you but I guess I must be a user of the site a bit longer before I can do that. Anyway, thanks for the solution galambalazs. I had no idea Javascript passed the function instead of the method. Lame! Anywho, on with the code. (That jsbin site is wicked cool too!)
dab
Have fun with coding! :)
galambalazs
+1  A: 

When a method belonging to an object is called using dot notation, its context (where this points to) is the object that it's connected to. For example:

// Context: `this` is `myObj`
myObj.myMethod();

However, when you store a reference to that function in a different variable, it loses that contextual relationship and the context becomes the global object:

// Context: `this` is the global object - `window` in a web browser
var myMethod = myObj.myMethod;
myMethod();

When executing a method, you can use its call or apply method to specify the context you would like it to execute in:

// Context: `this` is `myObj`
var myMethod = myObj.myMethod;
myMethod.call(myObj);

In your particular example, I would consider using a new method introduced in newer versions of JavaScript named bind. bind is supported by IE9, Firefox 4 and newer versions of Google Chrome and Safari, and it allows you to "lock" a function to a particular context. You can implement most of its functionality in browsers that don't support it using the following code (taken from the MDC documentation for Function.prototype.bind):

// PARTIAL WORKAROUND for Function.prototype.bind  
if (!Function.prototype.bind)  
    Function.prototype.bind = function(context /*, arg1, arg2... */) {  
        'use strict';  
        if (typeof this !== 'function') throw new TypeError();  
        var _slice = Array.prototype.slice,  
            _concat = Array.prototype.concat,  
            _arguments = _slice.call(arguments, 1),  
            _this = this,  
            _function = function() {  
                return _this.apply(this instanceof _dummy ? this : context,  
                    _concat.call(_arguments, _slice.call(arguments, 0)));  
            },  
            _dummy = function() {};  
        _dummy.prototype = _this.prototype;  
        _function.prototype = new _dummy();  
        return _function;  
};  

Using it in your code is very simple:

Foo.Init( Foo.onData.bind(Foo) );
Andy E
Thanks for the suggestion! I would rather not rely on a workaround however to get it to work with older browsers. :/
dab
@dab: it's not really a "workaround", it's just a less functional implementation. That missing functionality isn't required here. For what you need to do, *bind()* would have been the better solution because you're very much in control of the context from the point of reference (where you call the Init method). And for newer browsers, the result would have been even faster because there's a native solution.
Andy E