Javascript is a pretty great little language, but it can be a bit special at times. One particularly unintuitive aspect of Javascript is the this keyword. As a (former?) C++/C# developer, I've always had certain expectations as to how this behaves. Javascript does a pretty good job at confounding those expectations.
So what's up? Why is it that this gets all messed up when I execute my function after a timeout? Or when I assign my function to an event handler? Or when I pass it as a callback? What's going on here?In C++/C#, this is a reference (a pointer in C++) to the current instance of a class. When you call a function on an instance of a class, your this will point to the instance - predictably, reliably. This works because C# and C++ (and many other object oriented languages) make use of class-based inheritance. A function in a class belongs to that class, by definition, and when a new instance of the class is created, that object's functions are tightly bound to the instance.
In Javascript, functions are not bound to objects in the same way. In fact, Javascript does not actually make use of class-based inheritance as C++ does. It's inheritance model is prototype-based. When you go and new up an instance of a Javascript object, you're basically creating a new, blank object ({}) which inherits its properties from the prototype object. This newly instantiated object does not duplicate these properties, it references them. If you instantiate multiple objects based on the same prototype, they will all share the same prototype properties. Try it:
// create an empty constructor for our object.
var Obj = function() {};
// modify the prototype for Obj.
Obj.prototype.name = 'prototypeName';
// 'new' up a couple instances of Obj.
var y = new Obj();
var z = new Obj();
// modify Obj's prototype
Obj.prototype.name = 'replacedName';
// check our 'name' properties.
alert(y.name); // replacedName
alert(z.name); // replacedName
The upshot of this is that since prototype properties are shared (and functions on an object are just properties on that object), there is no real way for the function to know which object instance it belongs to. Where does Javascript's this come from, then? In Javascript, instead of being an intrinsic part of an object instantiation, this is evaluated when the function is executed, and is assigned based on how the function is called. Quite often, you will call a function on an object like this: myObj.myFunction(). By calling myFunction() like this, you're implicitly assigning this = myObj. That statement, myObj.myFunction() tells Javascript that, for the purposes of this execution of the function, myFunction is owned by myObj, and so this should be equal to myObj.
Now, this behaviour has some interesting consequences. Calling a function via its object, like myObj.myFunction() is pretty straightforward. The behaviour of this inside myFunction is what one would expect. However, in Javascript, you're free to pass functions around as much as you want - they're first-class citizens. So, lets look at an example. Lets see what happens when we pass our myObj.myFunction() in to setTimeout.
// define a global property, testValue
var testValue = 'GLOBAL test value';
// define the object prototype
var Obj = function() { };
Obj.prototype.testValue = 'PROTOTYPE test value';
Obj.prototype.myFunction = function() { alert(this.testValue); };
// construct the instance
var myObj = new Obj();
// execute myFunction after a 100ms delay.
setTimeout(myObj.myFunction, 100); // alerts with 'GLOBAL test value'
So, we've created our object, myObj, and we're calling its member function with a 100ms delay. What's interesting about this is that we're passing myFunction in to the setTimeout function. When setTimeout executes myFunction, it will do so directly: it will execute myFunction() instead of myObj.myFunction(). As far as setTimeout knows, we've simply passed in an arbitrary function. setTimeout is not aware that the given function is attached to an object. This means that when myFunction() is executed, it is executed without the context of its instance object. It is executed as though it has been detached from its object (which, in a way, it has). Javascript has no choice but to assign this to the global scope (window, in a browser).
Events behave similarly. When you do document.getElementById('myButton').onClick(myObj.myFunction), you're passing a function in anonymously. When the event is triggered, the browser will call it on the DOM element, like this: DOMElement.onClick(eventArgs). In this case, instead of this being assigned to the global scope (as above) or myObj (as one might expect), it's assigned to DOMElement. (Note that inline events behave differently again. Inline events, where you attach event handlers directly in the html, will be triggered by the browser in the global scope.)
I think you're getting the idea here, but lets quickly run through a couple more examples:
var testValue = 'GLOBAL scope test value';
var Obj = function() { };
Obj.prototype.testValue = 'INSTANCE test value';
Obj.prototype.myFunction = function() { alert(this.testValue); };
var myObj = new Obj();
// example 0.
myObj.myFunction(); // alerts 'INSTANCE test value'
// example 1.
var x = myObj.myFunction;
x(); // no object context. scope is global. alerts with 'GLOBAL scope test value'
// example 2.
var functionWithCallback = function(callback) {
callback();
}
// when callback() is executed, it has no object context.
// so, scope of this call is global.
functionWithCallback(myObj.myFunction); // alerts 'GLOBAL scope test value'
So what does this mean for our code. First of all: Javascript's this is not the same as in most languages. It behaves differently, and its behaviour can lead you into serious bugs if you're not careful. That said, it's really not so complicated once you understand the core concept - and Javascript's this actually allows you to do some pretty interesting things.
My next post, in a couple of days, will show you different ways to manage this in your code (to help avoid bugs), and will also demonstrate some of the benefits of Javascript's this.