开发者

Javascript Class + Properties not sticking to their values

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

     开发者_C百科   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!


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.


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


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜