Taming Javascript's 'this' Keyword
My last blog entry, Understanding Javascript's 'this' Keyword, explained how Javascript's this keyword is different from other languages, and what kinds of pitfalls to expect when using it. Here, I'll show you some techniques for getting this under control.
VAR SELF = THIS
Natively, Javascript has a few ways to help you deal with your this keyword. The first is quite simple: assign it to a variable, and take advantage of scoping. Note that this really only applies to inline functions, not so much to functions on an object.
var myValue = 'global value';
var Obj = function() {};
Obj.prototype.myValue = 'object value';
Obj.prototype.myFunction = function() {
// store this in self, so that we can use it in the setTimeout function.
var self = this;
setTimeout(function() {
// alerts 'object value'. self contains the value of this that we had in myFunction
alert(self.myValue);
// alerts 'global value'. when setTimeout calls the function, the calling context is lost and this is assigned to the global scope.
alert(this.myValue);
}, 100);
}
// run the code
var myObj = new Obj();
myObj.myFunction();
Lets go through this and understand what's happening. As you're aware, setTimeout() can cause havoc with your this, because it executes a function without the context of its calling object. That means that this is assigned the global scope, not your object. Here, we're assigning self = this before we call setTimeout. We're taking advantage of Javascript's scoping - the fact that inner functions can access the local variables of their outer functions. So when we assign self = this, we're storing the value of myFunction's this, and subsequently using that stored value, self, inside of our setTimeout function. Even though this inside our setTimeout will be incorrect, we've saved the correct this in self, so we're all good.
This isn't a solution to everything, but it's a quick way to ensure that your inline anonymous functions have a correct this. You still have to be careful when calling myFunction(), though, because all you're doing here is storing myFunction's this in the self variable. If myFunction's this is incorrect (as it would be if you called myFunction itself with a timeout or passed it as a callback), then var self = this; will do nothing to help.
CALL AND APPLY
Next up, there's Javascript's call and apply functions. These functions allow you to call a function while specifying a this. The only real difference between call and apply is that apply allows you to supply the arguments as an array; call expects them as individual arguments. Example:
// set a value in the global scope, so that we know when this == global
var myValue = 'global value';
// define a test object
var Obj = function() {};
Obj.prototype.myValue = 'function value';
Obj.prototype.myFunction = function() {
alert(this.myValue);
}
// instantiate the test object.
var myObj = new Obj();
// call myFunction() via myObj.
myObj.myFunction(); // alerts 'function value';
// assign myFunction to a local variable, then call it.
var fun = myObj.myFunction;
fun(); // alerts 'global value';
// now, use Javascript.call to execute the local copy of myFunction
fun.call(myObj); // alerts 'function value'
// Lets get a bit tricker: define another test object.
var Obj2 = function(){};
Obj2.prototype.myValue = 'OTHER function value';
var myOtherObj = new Obj2();
// Use call to override the default 'this' behaviour
myObj.myFunction.call(myOtherObj); // alerts 'OTHER function value'
BIND
Another useful technique is to bind your this to a function. There are several ways to do this. The first, most ideal way is to simply use ECMAScript 5's bind function. As with call and apply, you specify a this (and arguments, if you like) to your function. Unlike call and apply, the function is returned, not executed, so you can save it for later. When this new function is executed, it will be executed with your this applied.
Since bind is ECMAScript 5, browser support is less than perfect (go nuts in Node.js, however!). As such, there are a number of libraries that will be happy to help you out. underscore.bind(), prototype js bind(), etc... Most library bind() functions are simply wrappers around ECMAScript 5's bind that fill in the gaps when ECMAScript 5 is not available. (Beware of library functions named bind used for binding events... Not the same thing!)
CLOSURES
How do those libraries mimic ECMAScript 5's bind when it's not available? Closures. I intend to go deep on closures in another blog post, but briefly, a closure in Javascript is when a function contains a set of local variables, declares another function (which has access to those outer variables), and returns that inner function. Even though execution of the outer function is complete, and it has gone out of scope, the returned function will continue to have access to the local variables declared in the outer function. This allows you to do all kinds of neat things like, for example, create a wrapper around your function so that its this remains consistent.
function bind(context, functionToBind) {
return function() {
return functionToBind.apply(context,arguments);
}
}
var myValue = 'global value';
// define a test object
var Obj = function() {};
Obj.prototype.myValue = 'function value';
Obj.prototype.myFunction = function() {
alert(this.myValue);
}
var myObj = new Obj();
// bind myFunction's this. we replace the original function with the wrapped version.
myObj.myFunction = bind(myObj, myObj.myFunction);
myObj.myFunction(); // alerts 'function value'
.bindAll()
One last tip: .bindAll(). Underscore.js is an amazingly useful little library (we use it heavily at wavo.me),and it has a couple useful functions for managing this. There's .bind(), as I've already mentioned, but then there's .bindAll(). _.bindAll() will inspect the current object, and replace every function in the instance with a wrapped, bound function. Typical usage: in the constructor or initialization of your object, simply call _.bindAll(this). That'll bind your this to all of your object's functions, and you'll be free to stop worrying about this. Mostly. (Inline anonymous functions used as callbacks, since they're not members of your object and not bound, will still have to use the self = this trick above).