Javascript setInterval and objects

There is a problem with javascript's setInterval method and objects.

Take a look at the following example:

function Text (message) {
  this.message = message;
  this.printMsg = printMsg;
  this.start = start;
}

function printMsg () {
  alert (this.message);
}

function start () {
  window.setInterval (this.printMsg, 5000);
}

function startMe() {
  var t = new Text("Reload the page to stop me");
  t.start();
}

This is a simple object which should print a quite annoying alert every five seconds. If you try this code you will get an unexpected result:
<- After pressing, you have to wait for five seconds.

We have expected "Reload the page to stop me", but the output is "undefined". It seems, that this.printMsg is no function pointer to the objects method, but to the "static" or non-object function. Also using window.setInterval ('this.printMsg()', 5000) does not work. The problem occurs, because setInterval evaluates the first argument not in the start() routine, therefore this is not an instance of the current object.

This problem is not easy to solve, especially if we have more than one objects. To circumvent it, one has to save a reference to the object in the constructor and call the method later with this fixed reference:


function Text (message) {
  this.id = Text.Instances.length;
  Text.Instances[this.id] = this;

  this.message = message;
  this.printMsg = printMsg;
  this.start = start;
}
Text.Instances = new Array();
...
function start () {
  window.setInterval ('Text.Instances['+this.id+'].printMsg()', 5000);
}
...


It is important to use the quotations in window.setInterval ('Text.Instances['+this.id+'].printMsg()', 5000), because otherwise this.id is not defined (again, this is in this case no reference to the current object).
If someone knows a better solution (which also works with more than one object), please add a comment.

Update: Here you can find another explanation of the scope effect and a different solution.

The best solution

The best solution is shown below:

function Text (message) {
this.message = message;
this.printMsg = printMsg;
this.start = start;
var self = this;
}

function printMsg () {
alert (this.message);
}

function start () {
window.setInterval (self.printMsg, 5000);
}

function startMe() {
var t = new Text("Reload the page to stop me");
t.start();
}

I've found another good (at

I've found another good (at least - working) example of solving this problem: closures.

The method is described here. I hope it helps.

In short - forget about Text.Instances, try this:

function start () {
var selfobject = this;
window.setInterval (function(){selfobject.printMsg();}, 5000);
}

A Solution

Try this:

function myObject()
{
this._speed = 4000;
this._intID = 0;
}

myObject.prototype.myMethod = function() {
var instant = this;
this._intID = setInterval(function() { instant.aotherMethod(); }, this._speed);
}

myObject.prototype.aotherMethod= function() {
alert("It works");
}

Thanks for this!

Thanks for posting this. It really helped. I made one small change to my implementation though. If you implement a small singleton pattern like the following you can have a completely encapsulated version that the end user doesn't have to remember to build an array for:

function Text (message) {
this.id = Text.Instances.length;
if(!Text.Instances) Text.Instances = new Array();
Text.Instances[this.id] = this;

this.message = message;
this.printMsg = printMsg;
this.start = start;
}

...
function start () {
window.setInterval ('Text.Instances['+this.id+'].printMsg()', 5000);
}
...

After that change you can also change Text to a full class with the prototype property and also encapsulate the start function.

Just thought I'd share as a thank you! :)

Nice solution

I really like your solution. I didn't have any problems using it for multiple instances of a class as long as each object from the class is created using: objText = new Text(message);

There is another solution by including the Prototype JS framework, see the section in API -> Function -> bind.
However, I prefer your solution because that way I don't need to include that framework with all it's overhead.