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.
精彩评论