简体   繁体   中英

Invoke a non-global function from a javascript timer

Is it possible to somehow invoke a non-global function (ie a method of a class) from a timer?

The following example shows what I'd like to achieve:

function MyClass() {
  var msg = 'no message';

  // shows an alert after some delay
  this.delayedAlert = function(message) {
    msg = message;

    // global function works:
    //window.setTimeout('globalShow("'+message+'")', 1000);
    // this obviously also works:
    //this.show();

    // this doesn't work: *************
    // I'd like to invoke the show() method of this class after some delay
    window.setTimeout('this.show()', 2000);
  };
  this.show = function() {
    alert(msg);
  };
}

$(document).ready(function() {
  $('p').click(function() {
    var c = new MyClass();
    c.delayedAlert('hello');
  });
});

function globalShow(msg) {
   $('#hello').html(msg);
}

You can find a running sample here: http://jsbin.com/aqako5

function MyClass() {
  var msg = 'no message';

  // shows an alert after some delay
  this.delayedAlert = function(message) {
    msg = message;

    window.setTimeout($.proxy(this.show, this), 2000);
  };
  this.show = function() {
    alert(msg);
  };
}

Please use functions in setTimeout . Strings call eval and eval is like goto . Ie the raptors get you.

What's actually happening is that inside the function in setTimeout the this value is the default this value, ie window. You need to pass in a proxied method so that this is what you want this to be.

An alternative pattern to proxying in the correct this value is :

function MyClass() {
  var msg = 'no message';
  var that = this;

  // shows an alert after some delay
  this.delayedAlert = function(message) {
      msg = message;

      window.setTimeout(function() {
          that.show();
      }, 2000);
    };
    this.show = function() {
        alert(msg);
    };
}

This involves "caching" the correct this value in a separate variable. You still can't pass in that.show directly because that function will be invoked without knowing what this is.

This would help. Also to improve your class: if your class is not instantiated, it may cause problems. What would happen if someone called your class like so:

var myClass = MyClass();

The this inside MyClass would now be window, causing all sorts of problems and adding your properties to the global namespace.

To fix this, you need to make sure that when your class function is being called, that it was done with the new operator. You need to check inside MyClass that if this isn't a new instance of MyClass to force it to be a new instance. With my code below, you can call this method either of the following ways and have them both function the same way:

var myClass = MyClass();
// or
var myClass = new MyClass();

Change your class to this:

function MyClass() {
    // if this isn't an instance, make it so:
    if (!(this instanceof MyClass)) {
        return new MyClass();
    }

    var _this = $(this);    

    var msg = 'no message';
    var show = function() {
        alert(msg);
    };    

    // shows an alert after some delay
    this.delayedAlert = function(message) {
        msg = message;
        window.setTimeout(show, 2000);
    };
}

You can also improve this code by not creating a new function definition every time an instance of MyClass is created. Add your methods to the prototype of MyClass :

(function(window) {

    var MyClass = function() {
        // if this isn't an instance, make it so:
        if (!(this instanceof MyClass)) {
            return new MyClass();
        }
    };

    var msg = 'no message';
    var show = function() {
        alert(msg);
    }; 

    MyClass.prototype.delayedAlert = function(message) {
        msg = message;
        window.setTimeout(show, 2000);
    };

    window.MyClass = MyClass;

})(window);

You could declare an anonymous function:

window.setTimeout(function () {
    this.show();
}, 2000);

And, depending on the function, you could just pass the function itself to setTimeout :

window.setTimeout(this.show, 2000);

When you pass a string to setTimeout , you're basically telling the browser to wait a certain amount of time, and then eval the string.

When the browse eval s "this.show()" , it thinks that this refers to the global object, or the window .

The first suggestion uses closures , the second just passes the function itself (without executing it!), which we can do because in JavaScript, functions are first-class objects and can be treated just like any other variable.

Just keep in mind that when you use the second solution, you are divorcing the method from its context. If you try the following...

var msg = 'sadface';

var myObj = {
    msg: 'hello world',
    greet: function () {
        alert(this.msg);
    }
};

window.setTimeout(myObj.greet, 2000);

...then the this in greet will no longer refer to myObj . It will instead point to the window . It will alert sadface , not hello world .

Good luck.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM