0

I have this function in my object:

var time = {

    warps : 3,
    warpCounter : 50,

    warp : function(){
        if (this.warps > 0){
            this.warps--;
            this.warpLoop = 50;
            this.warpLoop(); 
        }
    },

    warpLoop : function(){
       setTimeout(function () {
          this.increment();              
          if (warpCounter--){
            this.warpLoop();
          }else{
            if(this.warps > 0){
              htmlInteraction.enableButton('warp-button');
            }
          }
       }, 100);
    },

};

When I try and call it from another method (using this.warpLoop()) I get:

Uncaught TypeError: Property 'warpLoop' of object #<Object> is not a function 

Why is this?

fredley
  • 32,953
  • 42
  • 145
  • 236
  • @dystroy I've added the whole object. – fredley May 21 '13 at 16:05
  • 1
    You need to read up on how `this` works in Javascript. It can be quite confusing until you understand it (and even more confusing if you think you understand it!). There's lots and *lots* of resources on the web about this; it's a very common problem. [This would make a good starting point](http://stackoverflow.com/questions/3127429/javascript-this-keyword), but there's plenty more if you search for it. – Spudley May 21 '13 at 16:08

2 Answers2

5

this context in setTimeout changes, you can use closures to keep the this context.

var test={
warpLoop : function(){
   var me=this;//set closure to get the right this context
   setTimeout(function () {
     console.log("this is:",this); // is window
     console.log("me is:",me); // is test
     // can call me.warpLoop() but not this.warpLoop()
   }, 100);
}
}
test.warpLoop();

your code can look like this:

var time = {

    warps : 3,
    warpCounter : 3,

    warp : function(){
        if (this.warps > 0){
            this.warps--;
            this.warpLoop = 50;
            this.warpLoop(); 
        }
    },

    warpLoop : function(){
       //the setTimeout calls me.warpCounter not this.warpCounter
       // wich is the same as window.warpCounter since the next
       // line is not part of the setTimeout execution you can
       // use this
       console.log("warpLoop called,warpCounter is",this.warpCounter);
       var me=this;
       setTimeout(function () {
          //me.increment();              
          if (me.warpCounter--){
            me.warpLoop();
          }else{
            if(me.warps > 0){
              //htmlInteraction.enableButton('warp-button');
            }
          }
       }, 100);
    },

};
time.warpLoop();
HMR
  • 37,593
  • 24
  • 91
  • 160
  • Something weird is going on, I can call `warpLoop` from outside the object, but not from another method within (I get the error in my question). Why is this? – fredley May 21 '13 at 16:17
  • @TomMedley I have included code specific to your situation. commented out the functions that you haven't posted because that will generate an error. – HMR May 21 '13 at 16:22
  • @TomMedley because you only have one instance of "time" you can access the properties with time.warpLoop as well. If you plan to create multiple instances I'd stick with using the this keyword and trapping value of "this" in a closure so you can use it in a timeOut, ajax, event handler callback or any asynch process that changes the "this" context – HMR May 21 '13 at 16:25
3

The this value in JavaScript is not lexically defined. It's defined by the manner in which the function was invoked.

A typical fix is to store the value of this in a variable in the enclosing scope, then reference it in the inner scope.

var that = this;

setTimeout(function() {
    that.whatever()
}, 1000)

While you could also bind the outer this value to your callback using Function.prototype.bind(), you seem to have an .increment() method that isn't throwing the error. So binding may break that.

  • I tried changing `warpLoop` to take an argument to `self` and passed it though - python style, but that didn't help... – fredley May 21 '13 at 16:08
  • @TomMedley: No, that wouldn't help unless you defined a `self` variable to reference the object. Basically, if you want the value of `this` in `warpLoop` to reference a particular object, you should invoke `warpLoop` from that object, as in `my_object.warpLoop()`. That's why we retain the outer `this` value in a variable. –  May 21 '13 at 16:11
  • You can use closures as in the example. Check out MDN for information on closures. – HMR May 21 '13 at 16:11