Objects, event handlers and "this" in javascript

One recurring problem I’ve had when using a function from a custom javascript object as an event handler is that when the event handler is called the “this” property of the function no longer references the object it originally belonged to.

To show you what I mean, take a look at this simple javascript object. It has a method called init() that is used as an event handler for the window’s load event:

  1. function MyObject(first, second){
  2.     this.first = first;
  3.     this.second = second;
  4.     events.addEventListener(window, "load", this.init);
  5. }
  6. MyObject.prototype ={
  7.     "init" : function(e)
  8.     {
  9.         alert(this);
  10.     }
  11. }
  12. var myObject = new MyObject("first", "second");

When the window’s load event is fired and init() is called, the function’s “this” parameter references the window object rather than myObject. In Firefox, Opera and Safari it’ll give you a reference to the object that triggered the event and in IE it’ll always give you a reference to the window object.

Why does it work like this?

As a C# programmer this is kind of unexpected. In C# the this keyword will always give you a reference back to the object that the method belongs to. That way you can access all the property goodness that the object has to offer when you’re handling the event.

This is because in C# you can’t call an instance method unless you pass a reference to the object that the method is to be applied to. This usually happens without you even having to think about it because most of the time you use the object to reference the method:

  1. myObject.myMethod();

In this case the myObject instance is really just an extra parameter to the myMethod() method that’s to the left of the method name rather than in the parameter list because of the conventions of C#. If you looked at what happened when the C# code was compiled to MSIL, you’d see the instance would be passed in exactly the same way as the other parameters.

In C# the instance parameter is always required. If you use a delegate to invoke a method without calling it from the instance object, C# will keep track of what object it belongs to and automatically pass the instance to the method. In Javascript the instance object parameter is optional. In most cases it’s implicitly passed because it’s usual to reference an object’s functions with the same instance.function() syntax that’s used in C#.

The scope problem only happens when you call the function without invoking it through the instance, like when you use an object’s member function to handle an event. Javascript doesn’t maintain a link to the instance object unless you explicitly provide one so it’s up to the code that invokes the function to provide an instance object if it wants by invoking the function using function.apply() or function.call(). This is what happens when the event fires. The event code invokes the function in this way and makes the element that triggered the event “this”.

Here’s how a function.apply() call would look (full example):

  1. function MyObject(name)
  2. {
  3.     this.name = name;
  4. }
  5. MyObject.prototype ={
  6.     "init" : function(e)
  7.     {
  8.         alert(this.name);
  9.     }
  10. }
  11. var myObject = new MyObject("helen's object");
  12. var functionVariable = myObject.init;
  13. functionVariable.apply(myObject);

I think it’s be designed that way because javascript functions are really just variables that can be referenced in multiple places just like any other type of object. It might be called myObject.myFunction() in one place and functionVariable in another place but both those names are just references to the same function.

Solution

The solution is to keep track of the object that you want to act as “this” yourself.

In most cases I end up use a custom function to hook up events to abstract away the difference between the IE attachEvent() method and the addEventListner() method the other browsers use. I just need to pass the object that I want to be “this” into this method and then when it comes time for the event handler to run, pass it back using Function.apply().

Javascript supports declaring dynamic functions so we can just wrap the event handler function inside another function that knows what the “this” parameter should be and then call the event handler (full example):

  1. helephant_events = {
  2.      "addEventListener" : function(element, eventName, eventHandler, scope)
  3.     {
  4.         var scopedEventHandler = scope ? function(e) { eventHandler.apply(scope, [e]); } : eventHandler;
  5.         if(document.addEventListener)
  6.             element.addEventListener(eventName, scopedEventHandler, false);
  7.         else if(document.attachEvent)
  8.             element.attachEvent("on"+eventName, scopedEventHandler);
  9.     }
  10. }

Now when we wire up the event, we just pass it a reference to “this”:

  1. function MyObject(first, second)
  2. {
  3.     this.first = first;
  4.     this.second = second;
  5.     helephant_events.addEventListener(window, "load", this.init, this);
  6. }
Posted on 26 Apr 08 by Helen Emerson (last updated on 26 Apr 08).
Filed under Javascript