As I’m sure we are aware by now, JavaScript is quite a remarkable language. One of the more interesting (and frustrating) aspects of the language is it’s object oriented nature. It borrows a considerable amount of its syntax from Java including the this keyword, which is what I’d like to discuss today. However, unlike Java (and other object oriented languages), the identity of this isn’t always clear.
this is a popular keyword for referencing the current object in a object oriented languages. What’s interesting though, is that scope is relative in JavaScript. This means that this could refer to any arbitrary object.
The ambiguity of this is the source of lots of misunderstanding so I’ll attempt to clarify things a bit here. Here are a few rules to keep in mind:
- this tries to refer to the object in which it is used.
- If used in a method in an object’s prototype, this will refer to the object’s instance.
- If used in an event handler, this
willshould refer to the element on which the handler is used. - These rules all go out the window when Function.call or Function.apply is used.
The first two rules should be obvious, however the second two deserve an explanation.
Events can get tricky
While the above examples are rather obvious, event callbacks get to be a bit tricky. To help us understand this concept a little better, lets assume the following function is defined:
var eventCallback = function () {
console.log('Am I the window?', window === this);
console.log('Am I a HTMLElement', !!this.nodeName);
};
this in the Standard Event Model
Event handers (in standard event models) will execute in the context of the event’s target element
// false, true
document.getElementById('some-link')
.addEventListener('click', eventCallback, false);
this in the Microsoft Event Model
Unfortunately, in the Microsoft world, things are different. Event handers are still executed in the global context.
Note: This only applies to IE6 and below.
// true, false
document.getElementById('some-link')
.attachEvent('onclick', eventCallback);
this in the Netscape Event Model
The Netscape event model simply copies a function into an object. this will always refer to the parent object (the elemet) as expected.
// false, true
document.getElementById('some-link').onclick = eventCallback;
Breaking the rules with call and apply
Since functions are objects in JavaScript, functions also have methods. Two very interesting methods on all functions are call which allows you to dynamically call a function with a fixed set of arguments, and apply which does the same thing except an array is used to pass argument values.
Function.call and Function.apply’s signatures look like this:
Function.call(Object scope [, mixed param [, ... ]]); Function.apply(Object scope [, Array params ])
The interesting part of both these methods is the first parameter: scope. This allows you to pass an object (yes, any object) in to be used as the scope in which the function is called. This is not only incredibly useful, but it’s also a way that many libraries and frameworks normalize browser inconsistencies (like events as mentioned above).
Consider the following:
var whoAmI = function (someObject) {
console.log(this === someObject);
};
var someElement = document.getElementById('some-element');
whoAmI(window); // true
whoAmI(someElement); // false
whoAmI.call(someElement); // true
whoAmI.apply(someElement); // true
Best Practices when Using this
- When possible, try to keep this usage to a minimum. Eliminate it if possible.
- When working with event callbacks, use a library (like YUI or jQuery) which automatically corrects the scope of the callback. You can roll your own, if you use Function.apply.
- When developing libraries, always keep event callbacks separate from application methods. For example, a callbacks property.
- Always functions which run in a different scope than expected.
Good stuff. Call and Apply are very cool, did not know about those.
One thing I don’t completely understand is:
“When possible, try to keep this usage to a minimum. Eliminate it if possible.”
I feel like I get a lot of value and a lot less typing using ‘this’. Maybe it’s creating problems I’m not aware of?
Something like this weak example: (assuming jQuery here)
myApp.module = {
settings: {
height: 22,
width: 500
},
createThingy: function ()
{
var thingy = jQuery('', {
css: {
width: this.settings.width,
height: this.settings.height
}
});
jQuery('body').append(thingy);
}
};
myApp.module.createThingy();
Pointless yes, but in something like that isn’t it better to use ‘this’ as opposed to:
myApp.module.settings.height;
Seems like the obj.obj chain would get pretty crazy, and not very portable?
Maybe there’s some magic I’m not aware of?? Speak it Mike!
Since you’re already referencing a nested object’s property a few times, you’re better off storing a reference to the parent object.
createThingy: function () {var settings = myApp.module.settings;
var thingy = jQuery('', {
css: {
width: settings.width,
height: settings.height
}
});
jQuery('body').append(thingy);
}
The benefit to manually specifying myApp.module.settings actually makes it even more portable since it can be called directly, from an event handler, or within the scope of any other object.