1
function Timer() {
 this.initialTime = 0;
 this.timeStart = null;

 this.getTotalTime = function() {
  timeEnd = new Date();
  diff = timeEnd.getTime() - this.timeStart.getTime();

  return diff+this.initialTime;
 };

 this.formatTime = function() {
  interval = new Date(this.getTotalTime());

  return  interval.getHours() + ":" +  interval.getMinutes() + ":" + interval.getSeconds();
 };

 this.start = function() {
  this.timeStart = new Date();

  setTimeout("this.updateTime()", 1000);
 };

 this.updateTime = function() {
  alert(this.formatTime());
  setTimeout("this.updateTime()", 1000);
 };
}


timer = new Timer();
timer.start();

I am getting an error:

this.updateTime is not a function

Any ideas?

Thanks

Jonah
  • 2,040
  • 7
  • 29
  • 32
  • I wonder if we can/should change this question to be a very generic one. All the code in the question didn't help me, I just went straight to the answer :). – Gezim Oct 13 '11 at 14:49
  • Turns out, `setTimeout` with a string as its first argument behaves like an _indirect_ eval. – Sebastian Simon Sep 30 '21 at 03:37

4 Answers4

2

Your string is not evaluated in the context of your object, so this doesn't refer to what you think it does.

You should not be passing a string argument to setTimeout. Instead, you should pass an anonymous function that calls your method with a saved copy of this.

For example:

var self = this;
setTimeout(function() { self.updateTime(); }, 1000);

The self variable is necessary because setTimeout's callback is also not evaluated in the context of your object.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • this works but.. is there a more elegant way? Without defining the global variable? – Jonah Apr 09 '10 at 01:53
  • 1
    That's a local, not a global. You could use a proxy function. – SLaks Apr 09 '10 at 01:55
  • Ok.. I'm not a JS expert by any means. Thanks! – Jonah Apr 09 '10 at 01:56
  • `self` is a regular local variable. Because the anonymous function is in the same scope, it saves references to all the local variables. This is an extremely powerful feature called a closure. – SLaks Apr 09 '10 at 01:58
  • Actually, if you define `self` inside the `updateTime` function, it's not guaranteed to refer to the `Timer` object either. There is no difference between your code and a direct `setTimeout(this.updateTime)`. `this`/`self` have the same context. :) – deceze Apr 09 '10 at 02:05
  • @deceze: You are wrong. The difference is in the invocation of updateTime. – SLaks Apr 09 '10 at 02:20
  • Exactly. `this` depends on how the function is invoked. `self` will be equal to `this`. It may work if the `Timer` object is calling functions on itself, but it can break if you invoke the function in a way that causes `this` to change to something else. – deceze Apr 09 '10 at 02:26
1

try

var me = this;
setTimeout(function() { me.updateTime() }, 1000);
user187291
  • 53,363
  • 19
  • 95
  • 127
1

is there a more elegant way?

Yes, in ECMAScript Fifth Edition:

setTimeout(this.updateTime.bind(this), 1000);

However, until all browsers support Fifth Edition (which they don't yet by a long measure), you should add your own implementation of Function.bind as fallback. eg.:

// Add ECMA262-5 method binding if not supported natively
//
if (!('bind' in Function.prototype)) {
    Function.prototype.bind= function(owner) {
        var that= this;
        if (arguments.length<=1) {
            return function() {
                return that.apply(owner, arguments);
            };
        } else {
            var args= Array.prototype.slice.call(arguments, 1);
            return function() {
                return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
            };
        }
    };
}

You might also prefer to use setInterval to avoid the repeated setTimeout calls. Finally, you should remember to declare all your variables (timeEnd, diff, etc.) as var, otherwise you're getting accidental globals, which can cause you horrible debugging heartache.

bobince
  • 528,062
  • 107
  • 651
  • 834
0

If you supply a string to setTimeout, this string will be executed literally. The thing is, it will be executed sometime later in the global context, outside your Timer object, so this means something completely different.

Just pass the function itself like so:

function Timer() {
  var self = this;   // make reference to this that won't change with context

  this.updateTime = function() {
    alert(self.formatTime());
    setTimeout(self.updateTime, 1000);
  };

}
deceze
  • 510,633
  • 85
  • 743
  • 889