JavaScript is an object oriented language. The first and foremost of JavaScript's objects is the window object. A window object is created whenever the browser loads a document with <body> or <frame>. This window object is the global space for functions and variables. Despite JavaScript providing native constructs for creating object oriented code, it is common to find code such as:
<script type="text/javascript">
function foo(value) {
window.alert(value);
}
window.onload(alert("Hello World!"));
</script>
Regardless whether the above script is in the head or body tag, the result would be that the function foo would be global. The relationship between the parent object and its properties and methods are indicated with dot notation. So, in the example above, the relation of the alert and onload methods to the window object is indicated.
In the context of the current object, the keyword "this" can be used to indicate the parent. As such, we could rewrite the previous example so:
<script type="text/javascript">
function foo(value) {
this.alert(value);
}
this.onload(alert("Hello World!"));
</script>
In the above example "this" refers to the parent object, or window. In JavaScript we do not always need to refer to the window object or the "this" keyword when we are dealing with variables and functions assigned to the global space. JavaScript has built checking to see what the scope for variables and functions are. When it sees they are not attached to any object, it assigns them to the window object. As such, we could just as easily have written the code as follows:
<script type="text/javascript">
function foo(value) {
alert(value);
}
onload = alert("Hello World!");
</script>
So far we've looked at a couple of very simple functions in the global space. Back in the 90's the majority of JavaScript code was just a collection of functions and variable in the global space. This meant that the more scripts you collected from different sources, the more likely you would run into problems where one variable or method would have the same name as some other one. A collision would occur in the global space and only one would win, leading to unexpected results difficult to troubleshoot.
That was the past. Now we are in a golden era of JavaScript development. We have well-engineered object oriented libraries such as Prototype, Scriptaculous, jQuery, YUI, Mootools, Dojo, Ext and many others which provide value to both the developer and end user.
When we start creating complex object oriented code, the "this" keyword becomes very practical for referring to the parent object. JavaScript's use of the "this" keyword can seem unusual if you are used to how other languages use it. To better understand the scope of the "this" keyword, we need to understand what JavaScript considers objects. JavaScript has an object constructor "Object" that we can use to create new objects. The following example illustrates this:
var myObj = new Object();
alert(typeof myObj);
// The result will be "object".
Functions are also objects in JavaScript. We can define a function using the "fuction" keyword:
function myFunc(val) {
this.value = val;
}
Or we could use an anonymous function assigned to a variable:
var myFunc = function(val) {
this.value = val;
}
We could also use the "Function" constructor, which tells JavaScript to create an object of type "function":
var myFunc = new Function("val", "this.value = val;");
As an example of object context and the "this" keyword, I'll create a object called ogitrev. First we'll set up its constructor using the JavaScript Function method. Then we'll add some members to it using prototype chaining.
ogitrev = new Function;
ogitrev.prototype = {
prev : function ( elem ) {
do {
elem = elem.previousSibling;
} while (elem && (elem.nodeType == 3 && !/\S/.test(elem.nodeValue)));
return elem;
},
last : function ( elem ) {
elem = elem.lastChild;
return (elem.nodeType == 3 && !/\S/.test(elem.nodeValue)) ?
ogitrev.prototype.prev( elem ) : elem;
}
}
In the above example, the prev method returns the node before the node indicated by the argument "elem" if that previous node is not an empty text node, otherwise it goes back until it find a previous node that is not an empty text node. This makes it possible for us to avoid empty text nodes will traversing the DOM. The method "last" also uses the "prev" method. However, since this is being called inside the "last" method, we must provide the full description of its relationship to the parent object. Since we are using prototype chaining to add members to the ogitrev object and the object has not be instantiated, we must include the prototype link between those members and the ogitrev object. As you can imagine, this could become quite cumbersome with complex code. Fortunately we can avoid this by using the "this" keyword. Thus we could write the last line of the "last" method as:
return (elem.nodeType == 3 && !/\S/.test(elem.nodeValue)) ?
this.prev( elem ) : elem;
So, anytime we need to use any of the methods that we add to the ogitrev object, we can invoke them using the "this" keyword. This works flawlessly, until we begin nesting functions inside of methods. Because functions in JavaScript are actually objects, descended from the Function object, when we nest one funciton inside of another, the scope of "this" gets reassigned to the nested function's parent. Actually, that is exactly what is happening with the ogitrev object. We created it as a function with Function as its constructor, then we assigned several functions to it as its methods. Therefore the use of the "this" keyword by those methods refers to ogitrev, and not to the global window object. This behavior in JavaScript can be confusing for developers used to other languages. Just remember, all functions are objects, so in nested functions, "this" will always refer to its immediate parent.
Practically speaking, deeply nested functions cannot use the "this" keyword to access the main object's members because "this" would have a different scope in those nested functions. There is, however, a way to get around this limitation. Here is some code illustrating this from my VX library. The shake method can reference its parent ogitrev's getStyle method with the "this" keyword. The shake method also creates an animation effect using the setTimeout function. Since that is a nested function, the "this" keyword would not refer to the ogitrev object but to the shake object. Since I need to call other ogitrev methods inside that setTimeout function, to preserve the reference to the "this" keyword pointing to the ogitrev object, I create a symbol: var that = this. By defining this symbol as a property of the shake method itself, we can use it regardless of how deeply nested a function may be and always have a reference to the shake method's parent object, ogitrev. If I didn't do this, I would have to write out each time: ogitrev.prototype.getStyle, etc. I like using "that" since I can see immediately that I am referring to the ogitrev object through assignment. You could name the symbol whatever you deem best.
shake : function ( elem, amount, delay ) {
// If the target element does not have any positioning,
// give it relative positioning.
if(this.getStyle(elem, "position") == "static") {
elem.style.position = "relative";
}
// Set a default value for the shake in case none
// was provided.
if (!amount) {
var amount = 5;
}
if (!delay) {
var delay = 100;
} else {
var delay = delay;
}
this.setLeft(elem, "-" + amount);
// Save reference to "this" keyword.
var that = this;
setTimeout(function() {
that.setLeft(elem, amount);
}, delay * 1);
setTimeout(function() {
that.setLeft(elem, "-" + amount);
}, delay * 2);
setTimeout(function() {
that.setLeft(elem, amount);
}, delay * 3);
setTimeout(function() {
that.setLeft(elem, "-" + amount);
}, delay * 4);
setTimeout(function() {
that.setLeft(elem, 0);
}, delay * 5);
}
This approach can solve many of our "this" headaches and render our scripts more legible, especially when working with JavaScript closures.
While creating my library, I ran into an interesting problem. I had created a special child object of ogitrev to store methods whose sole existence was as helper functions for the methods of the main ogitrev object. It's address was: ogitrev.util. Using prototype chaining, this became ogitrev.prototype.util. The methods inside of ogitrev.util needed a way to reference the parent ogitrev object so that they could call its methods. I could have just stuck with ogitrev.prototype.prev(), etc. but I wanted to find some way to use my "that" reference to the "this" keyword. I thought I would be able to declare it one time as a property of the ogitrev.prototype.util object. Look at the code below and think if you see any problems here.
ogitrev.prototype.util = {
that : ogitrev.prototype
}
The problem with the above example is that in order to access the property "that" inside my methods, I would have to use the "this" keyword:
ogitrev.prototype.util = {
that : ogitrev.prototype,
setPositioning : function ( elem ) {
if (this.that.getStyle(elem, "position") == "static") {
this.that.setAttr(elem, "positioning", "relative");
this.that.setStyle(elem, "position: relative;");
}
}
}
I think you'll agree that having to write this.that would be a bit strange. To avoid this I simply assigned the ogitrev.prototype object to a symbol in the methods where I needed it. Then it worked as normal:
ogitrev.prototype.util = {
setPositioning : function ( elem ) {
var that = ogitrev.prototype;
if (that.getStyle(elem, "position") == "static") {
that.setAttr(elem, "positioning", "relative");
that.setStyle(elem, "position: relative;");
}
}
}
Of course, as I mentioned previously, you could use any term you want for the symbol. The following would work just as well, but then you're running the chance that the same term might be used somewhere else.
ogitrev.prototype.util = {
setPositioning : function ( elem ) {
var o = ogitrev.prototype;
if (o.getStyle(elem, "position") == "static") {
o.setAttr(elem, "positioning", "relative");
o.setStyle(elem, "position: relative;");
}
}
}
This same technique can be used with automatic anonymous closures. Such closures fire as soon as they are loaded into memory. I use one in my library to get environmental variables and attach them as properties of the ogitrev object. Since the ogitrev object hasn't been instantiated, there is no way for it to know those values. The anonymous function allows me to query the system for the values I want and then attach them to the ogitrev object before it every gets called. The structure for an automatic anonymous closure is a little peculiar looking:
(function(){
// Function members defined here.
})();
The problem once again is that I have no context for referring to the ogitrev object. I really didn't want to write out ogitrev.prototype.yada-yada, so I used a symbol again:
(function() {
var n = navigator.userAgent.toLowerCase();
var that = ogitrev.prototype;
that.bV = navigator.appVersion;
that.bA = navigator.userAgent;
that.bName = "";
that.bNamePos = 0;
if (that.browser == "IE") {
that.vPos = that.bA.indexOf("MSIE");
that.browserVersion = parseFloat(that.bA.substring(that.vPos+5));
} else if (that.browser == "Opera") {
that.vPos = that.bA.indexOf("Opera");
that.browserVersion = parseFloat(that.bA.substring(that.vPos+6));
}
})();
So these are just a few examples of easy ways to handle object context in JavaScript.