This tutorial expands on the timer built in Countdown Timer Basics. In that example, we ended up with the following code for displaying a countdown timer in an HTML page.
var StartValue = 10;
setTimeout ("HandleTimeout()", 1000);
function HandleTimeout ()
{
var CountDownElement = document.getElementById ("countdown");
StartValue–;
CountDownElement.innerHTML = StartValue;
setTimeout ("HandleTimeout()", 1000);
}We will take this example and convert it to an object, so that a page can support multiple timers. Specifically, we will:
setTimeout function with a methodThe following examples make use of the prototype syntax used to define objects in JavaScript. See Objects In JavaScript for more information.
The first parameter to the setTimeout function can be a string or a function pointer. If it is a string, it is evaluated with eval(), or, more specifically, window.eval(). The distinction is important because it determines the execution context. That means that code passed to setTimeout is evaluated in the scope of the window—references to this will refer to the window, not your timer object.
How do we get around this limitation? Again, a quick search on the Internet will tell you that you have to pass a function pointer—and that you can’t use the this reference to call it. Most suggestions run along the lines of the following example:
MyClass.prototype.ScheduleTimer = function ()
{
var me = this;
setTimeout( function() { me.update (); }, 1000);
}Let’s take a look at what’s going on here:
this is assigned to the variable me, which is available at the global scope.[1] When setTimeout() executes the anonymous function, me refers to the correct object and will execute update() accordingly.
The example as written above has a problem: it doesn’t work when there is more than one MyClass defined on the page. If two timers are created and scheduled, the variable me will hold a reference to the second timer. When the two timers fire, they will both call update() on the second object.
The solution isn’t too difficult, but is also not so elegant. Using a global cannot be avoided; it’s the only common scope for the eval() context and MyClass. However, we can use the JavaScript Array object to keep track of multiple references and retrieve the correct one when the timer is fired.[2] This technique requires that the object is uniquely identifiable somehow (we use a Name property) and is shown below:
var Timers = new Array ();
MyClass.prototype.ScheduleTimer = function ()
{
timerName = this.Name
Timers[ timerName ] = this;
setTimeout( function()
{
Timers[ timerName ].update ();
}
, 1000);
}The code below makes use of techniques illustrated in the following articles. Some error-handling code has been left away for clarity’s sake.[3]
var Timers = new Array ();
function GenericTimer ()
{
this.Name = "";
this.Interval = 1000;
}
GenericTimer.prototype.ScheduleTimer = function ()
{
timerName = this.Name
Timers[ timerName ] = this;
setTimeout( function()
{
Timers[ timerName ].update ();
}
, 1000);
}
GenericTimer.prototype.Update = function ()
{
this.ScheduleTimer ();
}
CountdownTimer.prototype = new GenericTimer;
function CountdownTimer (numSeconds, elementName)
{
this.Name = elementName;
this.Seconds = numSeconds;
this.Element = document.getElementById (this.Name);
this.ScheduleTimer ();
}
CountdownTimer.prototype.update = function ()
{
this.Seconds–;
this.Element.innerHTML = this.Seconds;
this.ScheduleTimer ();
}Note that, as described in the final section of Inheritance in JavaScript, we cannot call the inherited update() method through a parent because the ScheduleTimer() method makes use of the instance variables, Name and Interval.
var are global. At the global scope, var is required; within a function, variables declared without var are available only within the function.↩