SitePoint Sponsor

User Tag List

Results 1 to 16 of 16
  1. #1
    SitePoint Evangelist winterheat's Avatar
    Join Date
    Aug 2007
    Posts
    508
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    setTimeout(this.doAnimation, 1000) will fail if it is defined in a class definition

    I did some animation using

    Code:
    var oDiv = {};
    oDiv.x = 100;
    oDiv.y = 100;
    oDiv.node = document.getElementById('divImage');
    oDiv.node.style.position = "absolute";
    
    oDiv.doAnimation = function() {
        oDiv.x += 1;
        oDiv.y += 1;
        oDiv.node.style.top = oDiv.y + 'px';
        oDiv.node.style.left = oDiv.x + 'px';
        setTimeout(oDiv.doAnimation, 33);
    }
    and it works fine. When it is changed to the traditional class definition, then it fails:

    Code:
    function Animation() {
        this.x = ...
        etc...
    
        this.doAnimation = function() {
            // do something
    
            // set delay
            setTimeout(this.doAnimation, 33);
        }
    }
    
    oDiv = new Animation();
    oDiv.doAnimation();
    which will fail. The second time when the doAnimation() is called, it seems that it doesn't have a concept of this.x, etc.

    It is the same if I use the more popular way of defining a class by moving the function out:

    Code:
    function Animation() {
        this.x = ...
        etc...
    }
    
    Animation.prototype.doAnimation = function() {
        //  do something
    }
    and it is the same, the second time when doAnimation() is invoked by the setTimeout, it doesn't have a concept of this.x.

    Does someone know what it is? hm... I don't need to use prototype.js and use something like doAnimation.bind(this) ? Can it be a self-contained script that doesn't rely on other framework? Thanks.

  2. #2
    SitePoint Evangelist
    Join Date
    Jul 2007
    Posts
    345
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    The problem is the this keyword which refers to the current context for a function. You should be able to solve the issue by using a different variable in the setTimeout.
    Code:
    function Animation() {
        this.x = ...
        etc...
    
        this.doAnimation = function() {
            // do something
    
            // set delay
            var that = this;
            setTimeout(that.doAnimation, 33);
        }
    }
    
    oDiv = new Animation();
    oDiv.doAnimation();

  3. #3
    SitePoint Evangelist winterheat's Avatar
    Join Date
    Aug 2007
    Posts
    508
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by r51 View Post
    The problem is the this keyword which refers to the current context for a function. You should be able to solve the issue by using a different variable in the setTimeout.
    so the only 2 changes are these?

    var that = this;
    setTimeout(that.doAnimation, 33);

    hm... i tried it and it didn't work. could it be that when window call doAnimation(), it is merely calling that function but not on the object?

    I even tried prototype.js and setTimeout(this.doAnimation.bind(this), 33);

    and it didn't work.

  4. #4
    SitePoint Evangelist winterheat's Avatar
    Join Date
    Aug 2007
    Posts
    508
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Correction:

    using this.doAnimation.bind(this) together with prototype.js actually works. I just needed to make sure all places that call setTimeout() use bind(this).

    So it looks like a common pattern that, if we want any callback or the function in setTimeout() to actually call the function ON this object, we need to use foo.bind(this) instead of foo.


    The only thing is instead of using the whole prototype.js, i would like to just use this bind() function instead... to save file size.

    someone also suggested using

    setTimeout(this.doAnimation, 1000, this);

    but said it works in IE 7 but not IE 6 and so forth.

  5. #5
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2008
    Posts
    5,757
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

  6. #6
    SitePoint Evangelist winterheat's Avatar
    Join Date
    Aug 2007
    Posts
    508
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    ah... can someone explain why

    Code JavaScript:
    var me = this;
     
    setTimeout(function() { me.doAnimation() }, 33);

    will work? and you can't use setTimeout(function() { this.doAnimation() }, 33);
    and you can't use setTimeout(me.doAnimation, 33) either...

  7. #7
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2008
    Posts
    5,757
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    this is a special keyword, and it's value depends on the context in which its used. me(or any other variable name) is not. In the case of
    Code:
    setTimeout(function() { this.doAnimation() }, 1600);
    By the time setTimeout executes the function, this no longer points to the same object, again because its a special keyword.


    The problem with
    Code:
    setTimeout(me.doAnimation, 33)
    Is that the value of me.doAnimation is passed into the setTimeout function immediately. We don't want that, we want setTimeout to get the value at a later time. So we wrap it in a function to prevent the value from being evaluated immediately. This also lets the value be revaluated each time the function gets called.


    The key things here are to create a variable named other than the this keyword, and then wrap it in a closure so that the method attached does not get executed until later.

    More on closures, 2nd paragraph in particular.
    http://www.jibbering.com/faq/faq_notes/closures.html

  8. #8
    SitePoint Evangelist winterheat's Avatar
    Join Date
    Aug 2007
    Posts
    508
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by crmalibu View Post
    this is a special keyword, and it's value depends on the context in which its used. me(or any other variable name) is not.

    The key things here are to create a variable named other than the this keyword, and then wrap it in a closure so that the method attached does not get executed until later.

    More on closures, 2nd paragraph in particular.
    http://www.jibbering.com/faq/faq_notes/closures.html
    ah i see... will read more about it later... i wonder if Ruby were used, do you actually need to do something like that too... is the major reason coming from the function being invoke by another object... probably the "window" object? so when window obj calls this.foo() the "this" is the window object.

    so if it is any callback function, you kind of have to do something similar, because it will be the another obj calling this.foo(). For example, it maybe the Ajax object in prototype.js calling this.foo().

  9. #9
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2008
    Posts
    5,757
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yup, setTimeout() is a method of the window object. So this would refer to window.

    Code JavaScript:
    function Person(name) {
        this.name = name;
        var me = this;
     
        this.SayName = function() {
            alert(this.name);
        }
     
        this.SayNameLater = function() {
            setTimeout(function() {
    // notice 'this' instead of 'me'
            this.SayName();
        }, 2000);
        }
    }
     
    // give the window object a SayName() method so javascript doesnt throw an error and choke...
    window.SayName = function() {
        alert("I'm the window!");
    }
    person1 = new Person('crmalibu');
    person1.SayNameLater();

  10. #10
    SitePoint Evangelist winterheat's Avatar
    Join Date
    Aug 2007
    Posts
    508
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I also tried

    Code JavaScript:
     
            <script>
     
                var foobar = "Hello";
     
                function Car() {
                    var me = this;
                    this.foobar = "Hi";
                    this.someFunction = function() {
                        alert(this.foobar);
                        setTimeout(this.someFunction2, 1000);
                    }
                    this.someFunction2 = function() {
                        alert(me.foobar);
                    }                            
                }
                aCar = new Car();
                aCar.someFunction();
     
    </script>

    and both times, it will say "Hi". If I use this.foobar inside for someFunction2, then it will be "Hi" and then "Hello".

    so as long as the function being called doesn't use "this", but use "me", then it is using the correct object.

    the "var me" in the constructor is a little bit weird, since "me" is not an instance variable... but is a local? I thought an object has instance variable and class variable but not local variable?

  11. #11
    SitePoint Evangelist
    Join Date
    Jul 2007
    Posts
    345
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    this.myVariable and var myVariable inside the constructor are both instance variables. Using this makes it 'public' whereas using var makes it 'private' to the instance.

    The function in the setTimeout maintains a reference to the variable via a closure. Even though the constructor has finished executing, a reference to me is maintained.

  12. #12
    SitePoint Evangelist winterheat's Avatar
    Join Date
    Aug 2007
    Posts
    508
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by r51 View Post
    this.myVariable and var myVariable inside the constructor are both instance variables. Using this makes it 'public' whereas using var makes it 'private' to the instance.

    The function in the setTimeout maintains a reference to the variable via a closure. Even though the constructor has finished executing, a reference to me is maintained.
    is this a viable practice? just use var me = this; on the very first line in the constructor

    and then just stop using "this" at all...

    use me.x = 123; or me.foo = function() { alert(me.x); } etc

    it seems to work with a test file:

    Code JavaScript:
            <script>
     
                var foobar = "Hello";
     
                function Car() {
                    var me = this;
                    me.foobar = "Hi";
                    me.someFunction = function() {
                        alert(me.foobar);
                        setTimeout(me.someFunction2, 30);
                    }
                    me.someFunction2 = function() {
                        alert(me.foobar);
                    }                            
                }
                aCar = new Car();
                aCar.someFunction();
     
            </script>
    but then i found that it will not work in:

    Code JavaScript:
           <script>
     
                var foobar = "Hello";
     
                function Car() {
                    var me = this;
                    me.foobar = "Hi";                
                }
     
                Car.prototype.someFunction = function() {
                    alert(me.foobar);
                    setTimeout(me.someFunction2, 30);
                }
     
                Car.prototype.someFunction2 = function() {
                    alert(me.foobar);
                }
     
                aCar = new Car();
                aCar.someFunction();
     
            </script>

    it says "me" is undefined. and if i add "var me = this;" to both functions, then the second time it will say "Hello" instead of "Hi".

  13. #13
    SitePoint Mentor NightStalker-DNS's Avatar
    Join Date
    Jul 2004
    Location
    Cape Town, South Africa
    Posts
    2,880
    Mentioned
    48 Post(s)
    Tagged
    0 Thread(s)
    I have not read thru everything on this thread, but one bit of advice that I can give instead of using

    setTimeout(someFunction(), 30);

    use:

    setTimeout('someFunction()', 30);

    the setTimeout method doesnt work correctly unless its in quotes

    Hope this helps

  14. #14
    SitePoint Evangelist winterheat's Avatar
    Join Date
    Aug 2007
    Posts
    508
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by NightStalker-DNS View Post
    I have not read thru everything on this thread, but one bit of advice that I can give instead of using

    setTimeout(someFunction(), 30);

    use:

    setTimeout('someFunction()', 30);

    the setTimeout method doesnt work correctly unless its in quotes

    Hope this helps
    oh, i think you line will work if it is

    setTimeout(someFunction, 30); // note without the ( )

    setTimeout takes a reference to a function as the first param. when you give it someFunction(), that is not specifically a reference to a function, it is what that function returns. someFunction, without the (), is a reference to a function.

  15. #15
    SitePoint Evangelist
    Join Date
    Jul 2007
    Posts
    345
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Declaring me using var in the constructor creates a private variable that is only accessible within the constructor.

    Code:
    <script>
    
       // a global variable, will become a property of the window object
       var foobar = "Hello";
                
       function Car() {
          // a private variable, only visible within this function
          // used to avoid the magic nature of the keyword 'this'
          var me = this;
    
          // same as this.foobar = "Hi"
          // the foobar property will be set on newly constructed Car instances
          me.foobar = "Hi";                
       }
     
       Car.prototype.someFunction = function() {
          // alert(me.foobar); me.foobar is undefined as me is undefined
    
           // sets me to refer to the current instance when someFunction is called
          var me = this;
    
          // will now display "Hi" as me refers to the current instance
          alert(me.foobar); 
    
          // You can wrap your function in quotes, with parentheses.
          // It is more common to see an anonymous function
          // The anonymous function maintains a reference to me
          setTimeout(function(){me.someFunction2();}, 30);
       }
                
       Car.prototype.someFunction2 = function() {
          // alert(me.foobar); me.foobar is undefined as me is undefined
    
          // as the function was called as a property of me
          // within the anonymous function, the context is correct
          // and 'this' refers to the current instance of Car
          var me = this;
    
          alert(me.foobar); // displays "Hi"
       }
                
       aCar = new Car();
       aCar.someFunction();
    </script>
    Last edited by r51; Oct 5, 2008 at 03:54. Reason: Original analysis was incorrect

  16. #16
    SitePoint Evangelist
    Join Date
    Jul 2007
    Posts
    345
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Tidying up
    Code:
    <script>
       var foobar = "Hello";
                
       function Car() {
          this.foobar = "Hi";                
       }
     
       Car.prototype.someFunction = function() {
          alert(this.foobar);
    
          var me = this;
          setTimeout(function(){me.someFunction2();}, 30);
       }
                
       Car.prototype.someFunction2 = function() {
          alert(this.foobar);
       }
                
       aCar = new Car();
       aCar.someFunction();     
    </script>
    There is no problem using 'this' in your code. In fact, I think it is generally clearer to do so.

    It is when a function is set to be called later, such as with setTimeout or as an event handler, that can change the context and thus the object pointed to by 'this'. In those circumstances a new variable can be used to point to the instance, a variable that will not 'magically' change what it is pointing to.

    An alternative to creating a new variable is to use a self-executing anonymous function. The extra variable is replaced by a function parameter:
    Code:
       Car.prototype.someFunction = function() {
          alert(this.foobar);
    
          (function(me){
             setTimeout(function(){me.someFunction2();}, 30);
          })(this);
       }


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •