I've always been taught the correct way to simulate a class in JavaScript is by adding methods to the prototype outside the function that will be your class, like this:
function myClass()
{
this.myProp = "foo";
}
myClass.prototype.myMethod = function()
{
console.log(this);
}
myObj = new myClass();
myObj.myMethod();
I've been running into the issue that the this
in my methods resolves to the global Window object, as explained best on quirksmode.
I've tried doing the var that = this;
trick Koch mentions, but since my methods are outside my class, my that
variable is no longer in scope. Perhaps I'm just not understanding it completely.
Is there a way I can create a class in JavaScript where methods are not recreated each implementation and this
will always point to the object?
EDIT:
The simplified code above works but I've had many times where I declare a "class" exactly like above and when I call myObj.myMethod()
, it comes back as a Window
object. I've read over every explanation of this
that I could find, such as the one I linked to and still don't understand why this problem sometimes happens. Any idea of a situation where the code could be written like above and this
would refer to Window
?
Here's the implementation I'm currently having problems with, but when I simplify it down like above into a few lines, I no longer have the problem:
HTML File:
<script type="text/javascript" src="./scripts/class.Database.js"></script>
<script type="text/javascript" src="./scripts/class.ServerFunctionWrapper.js"></script>
<script type="text/javascript" src="./scripts/class.DataUnifier.js"></script>
<script type="text/javascript" src="./scripts/main.js"></script>
class.DataUnifier.js:
function DataUnifier()
{
this._local = new Database();
this._server = new ServerFunctionWrapper();
this.autoUpdateIntervalObj = null;
}
DataUnifier.prototype.getUpdates = function()
{
this._server.getUpdates(updateCommands)
{
console.log(updateCommands);
if (updateCommands)
{
executeUpdates(updateCommands);
}
}
}
//interval is in seconds
DataUnifier.prototype.startAutoUpdating = function(interval)
{
this.stopAutoUpdating();
this.autoUpdateIntervalObj = setInterval(this.getUpdates,interval * 1000);
}
DataUnifier.prototype.stopAutoUpdating = function()
{
if (this.autoUpdateIntervalObj !== null)
{
clearInterval(this.autoUpdateIntervalObj);
this.autoUpdateIntervalObj = null;
}
}
main.js
var dataObj = new DataUnifier();
$(document).ready(function ev_document_ready() {
dataObj.startAutoUpdating(5);
}
I've cut out some code that shouldn't matter but maybe it does. When the page loads and dataObj.startAutoUpdating(5) is called, it breaks at the this.stopAutoUpdating(); line because this
refers to the Window
object. As far as I can see (and according to the link provided), this
should refer to the DataUnifier object. I have read many sources on the this
keyword and don't understand why I keep running into this problem. I do not use inline event registration. Is there any reason code formatted like this would have this problem?
EDIT 2: For those with similar issues, see "The this
problem" half way down the page in this Mozilla docs page: http://developer.mozilla.org/en-US/docs/Web/API/Window.setInterval
My favorite way of defining classes is as follows:
function defclass(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
Using the defclass
function you can define MyClass
as follows:
var MyClass = defclass({
constructor: function () {
this.myProp = "foo";
},
myMethod: function () {
console.log(this.myProp);
}
});
BTW your actual problem is not with classes. It's the way you're calling this.getUpdates
from setTimeout
:
this.autoUpdateIntervalObj = setInterval(this.getUpdates, interval * 1000);
Instead it should be:
this.autoUpdateIntervalObj = setInterval(function (self) {
return self.getUpdates();
}, 1000 * interval, this);
Hence your DataUnifier
class can be written as:
var DataUnifier = defclass({
constructor: function () {
this._local = new Database;
this._server = new ServerFunctionWrapper;
this.autoUpdateIntervalObj = null;
},
getUpdates: function () {
this._server.getUpdates(function (updateCommands) {
console.log(updateCommands);
if (updateCommands) executeUpdates(updateCommands);
});
},
startAutoUpdating: function (interval) {
this.stopAutoUpdating();
this.autoUpdateIntervalObj = setInterval(function (self) {
return self.getUpdates();
}, 1000 * interval, this);
},
stopAutoUpdating: function () {
if (this.autoUpdateIntervalObj !== null) {
clearInterval(this.autoUpdateIntervalObj);
this.autoUpdateIntervalObj = null;
}
}
});
Succinct isn't it? If you need inheritance then take a look at augment
.
Edit: As pointed out in the comments passing additional parameters to setTimeout
or setInterval
doesn't work in Internet Explorer versions lesser than 9. The following shim can be used to fix this problem:
<!--[if lt IE 9]>
<script>
(function (f) {
window.setTimeout = f(window.setTimeout);
window.setInterval = f(window.setInterval);
})(function (f) {
return function (c, t) {
var a = [].slice.call(arguments, 2);
return f(function () {
c.apply(this, a);
}, t);
};
});
</script>
<![endif]-->
Since the code is only executed conditionally on Internet Explorer versions lesser than 9 it is completely unobtrusive. Just include it before all your other scripts and your code will work on every browser.
The Answer
The problem is not with this.stopAutoUpdating();
, it is with:
setInterval(this.getUpdates, interval * 1000);
When you pass a function like this to setInterval
it will be called from the event loop, with no knowledge of the this
you have here. Note that this
has nothing to do with how a function is defined, and everything to do with how it is called. You can get around it by passing in an anonymous function:
var self = this;
setInterval(function(){ self.getUpdates(); }, interval * 1000);
In any modern engine you can use the much nicer bind:
setInterval(this.getUpdates.bind(this), interval * 1000);
You can also use bind
in older engines if you shim it first.
Understanding the Problem
I recommend that you read about call and apply for a better understanding.
Note that when you call a function normally, without using bind, call, or apply, then the this
will just be set to whichever object context the function was called from (that is, whatever comes before the .
).
Hopefully this helps you understand what I said about this
not being about how the function is defined, rather how it is called. Here is an example, where you might not expect this
to work, but it does:
// This function is not defined as part of any object
function some_func() {
return this.foo;
}
some_func(); // undefined (window.foo or an error in strict mode)
var obj = {foo: 'bar'};
// But when called from `obj`'s context `this` will refer to obj:
some_func.call(obj); // 'bar'
obj.getX = some_func;
obj.getX(); // 'bar'
An example where you might expect it to work, but it doesn't, along with a couple solutions to make it work again:
function FooBar() {
this.foo = 'bar';
}
FooBar.prototype.getFoo = function() {
return this.foo;
}
var fb = new FooBar;
fb.getFoo(); // 'bar'
var getFoo = fb.getFoo;
// getFoo is the correct function, but we're calling it without context
// this is what happened when you passed this.getUpdates to setInterval
getFoo(); // undefined (window.foo or an error in strict mode)
getFoo.call(fb); // bar'
getFoo = getFoo.bind(fb);
getFoo(); // 'bar'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With