25

Why can't I use setTimeout in a javascript object?

Message = function () {

    ...
    ...        

    this.messageFactory = ...
    this.feedbackTag = document.getElementById('feedbackMessages');

    this.addInfo = function (message) {
        var info = this.messageFactory.createInfo(message); // create a div
        this.feedbackTag.appendChild(info);

        setTimeout('this.feedbackTag.removeChild(info)', 5000);
        // why in here, it complain this.feedbacktag is undefined ??????

    };
}

Thanks for Steve`s Solution, now it will work if the code is as below... because the 'this' before was actually pointing to the function within setTimeOut, it cannot rearch Message.

Message = function () {

    ...
    ...        

    this.messageFactory = ...
    this.feedbackTag = document.getElementById('feedbackMessages');

    this.addInfo = function (message) {
        var info = this.messageFactory.createInfo(message); // create a div
        this.feedbackTag.appendChild(info);

        var _this = this;
        setTimeout(function() { _this.feedbackTag.removeChild(info); }, 5000);

    };
}

But why doesn`t it work if we do this:

Message = function () {

    ...
    ...        

    this.messageFactory = ...
    this.feedbackTag = document.getElementById('feedbackMessages');
    // public function
    this.addInfo = function (message) {
        var info = this.messageFactory.createInfo(message); // create a div
        this.feedbackTag.appendChild(info);

        delayRemove(info);

    };
    // private function
    function delayRemove(obj) {
        var _this = this;
        setTimeout(function() { _this.feedbackTag.removeChild(info); }, 5000);
    }
}
Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
jojo
  • 13,583
  • 35
  • 90
  • 123
  • possible duplicate of [How to access the correct `this` / context inside a callback?](http://stackoverflow.com/q/20279484/1048572) – Bergi Nov 14 '14 at 02:20
  • Duplicates: [setTimeout() inside JavaScript Class using “this”](http://stackoverflow.com/questions/5911211/settimeout-inside-javascript-class-using-this) and [Using setTimeout() within a JavaScript class function](http://stackoverflow.com/questions/6997921/using-settimeout-within-a-javascript-class-function) – handle Aug 29 '16 at 15:00

3 Answers3

84

Try replacing this line:

setTimeout('this.feedbackTag.removeChild(info)', 5000);

with these two lines:

var _this = this;
setTimeout(function() { _this.feedbackTag.removeChild(info); }, 5000);

Note:

Never pass setTimeout a string, as this invokes eval (which you should only use when necessary). Instead, pass setTimeout a function reference (this can be an anonymous function).

Finally, always check that the this keyword is pointing to what you think it points to (see http://www.alistapart.com/articles/getoutbindingsituations).

Addressing Question 2:

I believe that for normal functions, this is set to the window object—regardless of where they are declared. So moving the code into a separate function wouldn't fix the problem.

Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
Steve Harrison
  • 121,227
  • 16
  • 87
  • 72
  • 1
    Wouldn't that introduce a memory leak? – tsilb Jul 09 '09 at 03:49
  • 1
    @shrimpy: It's a complex problem, but this article describes it quite well: http://www.alistapart.com/articles/getoutbindingsituations. Basically, "this" doesn't point to the Message class inside the anonymous function. To fix this problem, we create a variable (I called it "_this"), make it point to the correct Message class, and then use this variable when we want to reference the class inside the anonymous function. – Steve Harrison Jul 09 '09 at 04:04
  • @tslib: In what way? This workaround is quite common. If you're worried that the anonymous function is going to keep things tied up, I'm pretty sure that it will be zapped by the Garbage Collector as soon as "setTimeout" has finished with it. – Steve Harrison Jul 09 '09 at 04:16
  • @tsilb: Whoops—I spelt your username incorrectly in my previous comment! Sorry! – Steve Harrison Jul 09 '09 at 04:18
  • Wonderful solution. Never knew you could pass an anon function! – Subimage May 04 '12 at 05:34
  • I think it's because setTimeout is a Window method, when it calls the callback anonymous function, 'this' refers to the window object. Since setTimeout is a method from window, the anonymous function receive 'this' as the context being Window object. – Not a privileged user Aug 10 '12 at 01:38
  • Surprising, nobody used the word *closure* yet ... ;-) – Déjà vu Nov 04 '12 at 15:10
  • Maybe because there isn't any _closure_ in the example? ;) – Wirone Nov 27 '12 at 10:36
  • 1
    There *is* a closure, as the anonymous function accesses the `_this` variable, which is outside of its own immediate lexical scope. – yerforkferchips Sep 01 '14 at 14:16
9

A neater way is to just pass this as an argument to the function being called in the timeout:

function delayRemove(obj) {
  setTimeout(function(_this) {
      _this.feedbackTag.removeChild(obj);
    }, 5000, this);
}

You should really pass obj as an argument as well, just to make sure it is in scope (the number of parameters is unlimited):

function delayRemove(obj) {
  setTimeout(function(_this, removeObj) {
      _this.feedbackTag.removeChild(removeObj);
    }, 5000, this, obj);
}

HTML5 and Node.js extended the setTimeout function to accept parameters which are passed to your callback function. It has the following method signature.

setTimeout(callback, delay, [param1, param2, ...])

As setTimeout isn't actually a JavaScript feature your results may vary across browsers. I couldn't find any concrete details of support, however as I said this is in the HTML5 spec.

Community
  • 1
  • 1
Luca Spiller
  • 2,248
  • 4
  • 24
  • 28
2

To answer your last question: "Why doesn`t it work if we do this":

Message = function () {

...
...        

this.messageFactory = ...
this.feedbackTag = document.getElementById('feedbackMessages');
// public function
this.addInfo = function (message) {
    var info = this.messageFactory.createInfo(message); // create a div
    this.feedbackTag.appendChild(info);

    delayRemove(info);

};
// private function
function delayRemove(obj) {
    var _this = this;
    setTimeout(function() { _this.feedbackTag.removeChild(info); }, 5000);
}}

It's not working because you are passing an undefined variable (info) instead of a defined variable (obj). Here is the corrected function:

function delayRemove(obj) {
var _this = this;
setTimeout(function() { _this.feedbackTag.removeChild(obj); }, 5000);}
Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
ecartsiger
  • 21
  • 1