February 23, 2009

Javascript objects: And what is this?

Posted in Software at 03:06 by graham

When writing object-oriented Javascript, there are two occasions when you need to be careful that this is set correctly: In inner functions and in callbacks.

this in inner functions

  1. If you are not in an object, this refers to the global window object.
  2. If you are in an object’s method, this refers to that object,
  3. except in an inner function, when this refers to the global window object again.

Number 3 is what you need to watch for. It is considered a bug in Javascript. Here is an illustration of the three cases:

// You need Firebug to run this script, otherwise
// replace console.log with alert.

console.log('Globally: '+ this);

var MyObj = function() {  // Constructor
}
MyObj.prototype = {
   test:
      function() {

         function inner() {
            console.log("In an inner function: "+ this);
         }

         console.log("In an object's method: "+ this);
         inner();
      }
}

var m = new MyObj();
m.test();

The Firebug console will display this:

Globally: [object Window]
In an object's method: [object Object]
In an inner function: [object Window]

To get inner functions to see your object, you need to copy this into a local variable. Thanks to closures inner functions can see all the local variables of the containing function.

var MyObj = function() {  // Constructor
}
MyObj.prototype = {
   test:
      function() {

         function inner() {
            // Using _this instead of this
            console.log("In an inner function: "+ _this);
         }

         // Save in local variable
         var _this = this;
         console.log("In an object's method: "+ this);
         inner();
      }
}

var m = new MyObj();
m.test();

This will display what you expect:

In an object's method: [object Object]
In an inner function: [object Object]

this in object callbacks

Object scope is not preserved when you pass one of your object’s methods as a callback. Example:

var MyObj = function() {
}
MyObj.prototype = {

   test:
      function(){
         console.log('test: '+ this);
         setTimeout(this.onEvent, 1);
      },

   onEvent:
      function() {
         console.log('onEvent: '+ this);
      }
}
var m = new MyObj();
m.test();

This prints:

test: [object Object]
onEvent: [object Window]

This is the case because when you pass this.onEvent as a callback, you are passing a function, like you might pass a string or an integer. The setTimeout function doesn’t know that it belongs to an object.

The answer is again to use the closure (that’s often the answer in Javascript), and wrap your call in an inner function, remembering the previous point about inner functions.

var MyObj = function() {
}
MyObj.prototype = {

   test:
      function(){
         console.log('test: '+ this);
         // save locally
         var _this = this;
         // wrap and call
         setTimeout(function() {_this.onEvent()}, 1);
      },

   onEvent:
      function() {
         console.log('onEvent: '+ this);
      }
}
var m = new MyObj();
m.test();

As expected, this prints:

test: [object Object]
onEvent: [object Object]

We save this in a local variable, then pass an inner function as the callback. That function has no context, but it does have a copy of our object saved in it’s _this variable. It calls onEvent on our object.

Happy Javascripting!

3 Comments »

  1. Mark Estes said,

    April 13, 2009 at 19:47

    Perfect clip!

    You gave a very clear explaination of an important concept.

    Thanks

  2. Steve Knight said,

    February 25, 2009 at 08:06

    Wow. I had no idea. Thanks for that.

    The bug, I guess, is that the inner function should automatically be a closure around the ‘this’ of the defining scope?

  3. Graham King » Javascript objects: And what is this? said,

    February 23, 2009 at 08:53

    […] Read the original post: Graham King » Javascript objects: And what is this? […]

Leave a Comment

Note: Your comment will only appear on the site once I approve it manually. This can take a day or two. Thanks for taking the time to comment.