Javascript method context

In the previous examples you might have noticed that we use this to get a reference to the object that a function belongs to while we’re inside the function. The value of the this operator is the context of the method:

  1. function Pet(name, species, hello){
  2.     this.name = name;
  3.     this.species = species;
  4.     this.hello = hello;
  5.     this.sayHello = function()
  6.     {
  7.         alert(this.hello);
  8.     }
  9. }
  10. var rufus = new Pet("Rufus", "cat", "miaow");

If you’re familiar with languages like C# or Java, chances are you’ve never really thought too much about this because its value always references the object that the method belongs to (the rufus object in the previous example). This is usually the case in javascript too but there are some situations where it won’t refer back to the object that you’re expecting.

Functions are first class objects

The difference between way the this operator works in C# and javascript is a side effect of functions being first class objects in javascript. Functions are just another type of variable that can be passed around the application.

The methods in a javascript object are only methods because you chose to store a function inside one of the object’s properties. There is nothing in the language that always binds the method to the object. If you wanted to, you could use the same function in a number of different objects:

  1. var sayHello = function()
  2. {
  3.     alert(this.name + " says hello");
  4. }
  5.  
  6. var rufus = {
  7.     name: "Rufus"
  8. }
  9. rufus.sayHello = sayHello;
  10.  
  11. var sabby = {
  12.     name: "Sabby"
  13. }
  14. sabby.sayHello = sayHello;
  15.  
  16. // invoke sayHello from the objects
  17. rufus.sayHello();
  18. sabby.sayHello();

Complete example

Notice that the context of the sayHello() function (the thing that this references) is different depending on which object invoked it. In this example the two different objects were pretty much the same but the sayHello() function could be used by completely different objects as long as they provide the right information.

This design decouples the function from the context that uses it. You can plug any context into the function and it will still work. This is one of the reasons that having functions as first class objects is such a powerful feature.

Default context

Every time you call a function it has a context even if it is not explicitly provided. If no context is explicitly provided when the function is called, the default context will be used instead. In the browser the default context is the window object. This means that if you call a function without invoking it through an object, this will be set to the window.

To see this in action lets try calling sayHello() without invoking it from an object:

  1. var sayHello = function()
  2. {
  3.  if(this.name)
  4.   alert(this.name + " says hello");
  5.  else    
  6.   alert(this + " can't say hello because name property was not set");
  7. }
  8.  
  9. // invoke sayHello from the global context
  10. sayHello();

Complete example

Javascript is happy to run the function without calling it through an object. It just sets the context to the default context, the window object. The window object doesn’t have a name property so instead of alerting “someone says hello”, instead it alerts the error message about the object not having a name property.

The way I imagine it is calling sayHello() is really calling window.sayHello(). You just don’t need to explicitly use the window object to invoke it because the window object is the default context. The same thing happens if you set a variable without declaring it first using the var statement. Instead of creating a new variable that’s only available in the current scope, it actually creates a new property on the window object:

  1. flibble = "xyz";
  2. alert(window.flibble);

Complete example

Check out this article if you want to find out more about what happens when a Javascript function is called. It’s a really interesting write up that goes into lots of detail.

Objects, context and event handlers

In the previous examples, we have explicitly called the sayHello() method ourselves. This doesn’t happen for functions that are used for event handlers. We wire them up and then they are invoked automatically when the event occurs.

This can be a problem for functions that actually are methods of objects. The logic that invokes the event handler doesn’t know anything about the object the method belongs to so it can’t be invoked in the right context.

Instead the context of the method is set to the element that caused the event. If it was a button click event, it will be set to the HTML button element that was clicked. It it is an onload event, it will be set to the element that was loaded. The exception to this rule is IE where the context will always be set to the default window context.

Here’s an example where the hello function of a rufus object is used for the onclick event of our button:

  1. var rufus = {
  2.     name : "Rufus",
  3.     species: "cat",
  4.     hello : function() { alert(this.name + " says miaow"); }
  5. };
  6. document.getElementById("button").onclick = rufus.hello;

Complete example

If you’d never tried this before, you’d probably expect the this operator to still be set to the rufus object when the button is clicked so the output would be “Rufus says miaow”. Instead the this operator is set to the button that triggered the event. The button’s name property is set to “button” so the output is “button says miaow” (which is just silly, buttons don’t miaow).

Changing the context using Function.apply() and Function.call()

In cases like the event handler problem, you might want to invoke a method in an explicit context. Javascript functions support two methods that you can use to do this called Function.apply and Function.call.

Both methods are pretty much the same. You can use them both to invoke a method, supply the context and a list of parameters. The only difference between the two are the way the parameters are passed in. Function.apply() accepts the parameters for the function as an array while Function.call() accepts them as individual parameters:

  1. var sayHello = function()
  2. {
  3.     if(this.name)
  4.         alert(this.name + " says hello");
  5.     else
  6.         alert(this + " can't say hello because name property was not set");
  7. }
  8.  
  9. var rufus = {
  10.     name: "Rufus"
  11. }
  12.  
  13. sayHello.call(rufus);
  14. sayHello.apply(rufus);

Complete example

Using Function.call() to solve the event handler problem

Remember the problem with using a method from an object as an event handler? When the event is fired, the context of the event handler is set to the HTML element that triggered the event. Then when you access the this operator you get a reference to the HTML element rather than the object that you might have expected.

It’s possible to use Function.call() (or Function.apply() if you want) to invoke the event handler in the right context. The plan: create a custom function that wires up the event handler and knows what the context of the function should be when the event is run:

  1. function addEvent(element, eventName, handler, context)
  2. {
  3.     var wrapper = handler;
  4.     if(context)
  5.     {
  6.         // create an anonymous function
  7.         // that uses a closure to access the context parameter
  8.         // and then uses Function.call() to invoke the real event handler
  9.         wrapper = function(e) {
  10.             handler.call(context, e);
  11.         }
  12.     }
  13.     if(element.addEventListener)
  14.         element.addEventListener(eventName, wrapper, false);
  15.     else if(element.attachEvent)
  16.         element.attachEvent("on" + eventName, wrapper);
  17. }
  18. var rufus = {
  19.      name : "Rufus",
  20.      species: "cat",
  21.      hello : function() { alert(this.name + " says miaow"); }
  22. };
  23. addEvent(document.getElementById("button"), "click", rufus.hello, rufus);

Complete example

This works because javascript has a feature called anonymous functions that you can use to create a new function at runtime. The addEvent function uses this to create a new wrapper function that calls the event handler using Function.call(). The wrapper function is passed to addEventListener() instead of the event handler that was passed in as a parameter.

The wrapper function can access the context object when the event fires because of another javascript language feature called closures. When you create a function inside another function, closures mean that the inner function automatically has access to the local variables of the outer function (addEvent) when the inner function (wrapper) is called. This works even if the inner function is called after the outer function has finished running.

This is how the wrapper function can get hold of addEvent’s context and handler parameters when the anonymous function is run.

Mostly if you’re using a javascript framework, you don’t need to worry too much about this. The framework will look after it for you, usually in a way similar to this. For example, if you’re an ASP.NET ajax programmer, this is exactly what happens when you use Function.createDelegate() to register an event handler inside an component.

What’s next?

Sometimes you need to find out things about an object’s capabilities. The next article talks about finding out what type of object you have and what it can do.

This article is part of a set of related posts about How javascript objects work.

Posted on 29 Nov 09 by Helen Emerson (last updated on 08 Jan 10).
Filed under Javascript, Web development