2

I'm getting some odd behaviour when trying to use setInterval within an object.

Here is my code:

var Person = {
    speech: null,

    tryToSpeak: function ()
    {
        this.speech = "hello";
        self.setTimeout (this.speak, 1000);
    },

    speak: function ()
    {
        // prints out undefined
        console.log (this.speech);
    }
}

Person.tryToSpeak ();

When speak() is run via setTimeout() it doesn't have access to any of the objects data such as speech. What the hell is going on? Is this unavoidable behaviour?

1
  • This is the expected behavior. The this value in JavaScript is very dynamic. Its value is based on how the function is called, not where the function was defined. Commented Nov 26, 2012 at 18:28

5 Answers 5

3

The methods don't carry the object information with them. You can use .bind to ...bind the method to an object:

window.setTimeout(this.speak.bind( this ), 1000);

Read more about javascript this keyword

Sign up to request clarification or add additional context in comments.

4 Comments

.bind is a new keyword which is introduced in JavaScript 1.8.5. It might not be supported by all the browsers.
@AlexanderStepaniuk Well it's not a keyword. And yeah, it's covered in the link I gave.
So, you know it's not a crossbrowser solution for now :). To make it crossbrowser and also applicable for some non-browser javascript code we need to implement bind() by ourself.
@AlexanderStepaniuk The link has the code for .bind, and it works crossbrowser.
2

Some notes first:

  • In general, the convention in javascript is to reserve upper case names for "class" definitions that will have "instances" created using the new keyword. In my answer I will use person instead of Person.
  • setTimeout is a method of the window object. self.setTimeout is not correct. While in some javascript implementations self is the window, in others it is not, so it is unreliable.
  • this always refers to the current execution context, no matter when or how that is happening. The setTimeout call wholly takes the function out of its normal object context--it is just a function at that point, so when executed it does not have the expected "this" object.

I can see a few options to get around this.

  1. Use bind to "create a new function that, when called, has its this keyword set to the provided value.":

    window.setTimeout(this.speak.bind(this), 1000);
    
  2. Wrap the function passed to setTimeout in an anonymous function that sets this dynamically:

    window.setTimeout(function(thisobj) {
       return function() {
          thisobj.speak.call(thisobj);
       }
    }(this), 1000);
    

    We're using a function to create a closure over the thisobj parameter, which happens to have been called using the this that was the current this object at the time of invoking setTimeout. The function then itself returns a function for setTimeout to call, and that function uses call (or you could use apply) to set the this object for the invocation of speak. All we've really done here is duplicate the functionality of bind without its parameter support--so use bind instead unless you need full cross-browser and old-browser support (in which case you can do this or you can sugar up your javascript so bind works, with the code at the bottom of my answer). But I wanted you to see what's going on under the covers.

  3. Access members of your person object explicitly:

    speak: function () {
        console.log (person.speech); // instead of this.speech
    }
    
  4. Change Person to be an object constructor function, which can be used to create a new "person" instance:

    function Person() {
        var me = this;
        this.speech = null;
        this.tryToSpeak = function () {
            me.speech = "hello";
            window.setTimeout(me.speak, 1000);
        };
        this.speak = function () {
            console.log(me.speech);
        };
    }
    
    var person = new Person();
    person.tryToSpeak();
    

    By capturing a copy of this as the private variable me inside the constructor function, you can use it later in your other methods. The this that is captured now makes sense because when you run new Person() there is an execution context (unlike when simply declaring an object the way you did, the execution context is window). A big drawback of this method is that every instance of the object will have its own copies of the functions, as they can't be part of the object's prototype (in order to have access to private variable me);

I am sure that there are other ways possible to handle it. You didn't explain why you need person to be an object in the first place, so I don't know the best pattern to accomplish your goals. You may not need multiple person objects, but then again you might. Some broader understanding of the context and purposes afoot would help me guide you better.

Note: to "shim" javascript implementations that don't have bind, so you can use bind on functions more easily, you can do this (note there are some differences from the native implementation, see the same bind link as above for details):

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5 internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP && oThis
                                 ? this
                                 : oThis,
                               aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}

3 Comments

Note though, that self often actually is a reference to the window object. Just test self === window in a console.
The advantage of #2 over #1 is that you don't have to shim bind in those environments that don't support it natively.
Added another answer because my changes were too long for comments.
1

This is unavoidable. When you pass functions around like that they lose their execution context.

The solution is to capture the "this" variable:

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

5 Comments

You're implicitly making a global variable. Use var to declare self in the local scope.
Perhaps better than 'self' would be 'person'; it's more descriptive, and 'self' often has a different meaning in client-side JS.
@Scott I like me instead.
@ErikE: Yes, although I wouldn't have used it the way you did, probably only within the tryToSpeak function.
@ScottSauyet Well, six on one hand, and half a dozen on the other. I changed my code to use this where possible.
0

A somewhat different version of ErikE's fourth suggestion that does much the same job but with what I think is simpler code is

function Person() {
    this.speech = null;
}
Person.prototype.tryToSpeak = function () {
    this.speech = "hello";
    var person = this;
    window.setTimeout(function() {person.speak();}, 1000);
};
Person.prototype.speak = function () {
    console.log(this.speech);
};


var person = new Person();
person.tryToSpeak();

As Erik said, it's not really clear if you even need multiple Person objects, but if you do, this sort of technique might be simplest.


UPDATE (based on ErikE's comments): This version adds a name to the Person, and passes the speech in as a parameter to make it clear exactly who is saying what. The basic structure remains the same.

function Person(name) {
    this.name = name;
}
Person.prototype.tryToSpeak = function (speech) {
    var person = this;
    window.setTimeout(function() {person.speak(speech);}, 1000);
};
Person.prototype.speak = function (speech) {
    console.log(this.name + " says, '" + speech + "'.");
};

var lincoln = new Person("Abraham Lincoln");
var churchill = new Person("Winston Churchill");
lincoln.tryToSpeak("Four score and seven years ago...");
churchill.tryToSpeak("Never, never, never give up.");

The main point is that the person variable in tryToSpeak is a local variable stored in a closure and not a reference to some singleton.

3 Comments

Please don't vote for this answer, though. All the hard work was done by ErikE. Mine was simple code clean-up.
@ErikE: Actually, the reference to person is stored in a local closure, so that is not actually an issue. I will add an update with another version that makes it clear, though.
Sorry about that, Scott. I didn't pay attention. It's obvious from var person = this that you had a closure, I just didn't expect it because of my own code usage of person being an instance of Person, and that in general I expect closures of this kind to be accomplished with 'me' or somesuch instead of naming it. My error.
-2

I don't know why you got minuses. This is a typical scope/reference error. Think of it one minute, what could be this in speak ? Answer : nuts.

So you can either reference the object directly, like this :

 // prints out Hello
 console.log (Person.speach);

Or pass it as an attribute to speak, like that :

self.setTimeout (this.speak(this), 1000);

Jsfiddle with both cases :

http://jsfiddle.net/eukQH/

5 Comments

You are calling the method immediately and passing undefined (the return value of the method call) to setTimeout in the second case
in both cases, I access the value, sooo
The point is that you're executing the speak function immediately, not after a 1000ms delay.
Pay careful attention to your fiddle. The logging occurs immediately, not after 1 second.
sooo... of course you access the value. You're not actually using setTimeout

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.