JavaScript's this
keyword doesn't have to be confusing. This tutorial will help you use it correctly and effectively.
JavaScript is designed to support true object-oriented programing without the use of classes. That means the familiar OOP features are available, but are sometimes implemented in a novel way. One such feature is method invocation and the associated this
keyword. This is a common stumbling block for beginners, because JavaScript provides some helpful magic that hides what's really going on. However, this obstacle is easily overcome by a quick peek under the hood, which is why we're here. Once we've done the theory, we'll cover the top 5 issues and idioms you Need To Know.
What is a "scope," anyway?
When a JavaScript function is called, it is executed within a "scope." The scope is just some Object or type. The this
keyword provides a reference to the scope. Whenever we call a function with a scope, we say that we "bind" the function to the scope. Don't be misled by the terminology; binding is temporary and only applies to a single execution.
Think of scope as a special hidden argument that is always passed into a function as this.
In fact, JavaScript provides a way to do exactly that: the .call()
method of Function. To see .call()
in action, let's write a function that simply tells us what this
and the first argument is:
function expose(x) {
alert( this );
alert( x );
}
If we simply call expose()
as a regular function:
expose( 'something' );
We see that this
is undefined
or the Window
. If we instead use the .call()
method of expose()
:
expose.call( 'scope', 'something' );
Then we see that this
is now "scope". So .call()
shows us what's "really" going on (passing in this
as special argument), while the normal function call syntax "hides" the this
argument.
Dot parentheses is magic
If .call()
was the only way to bind a function to a scope then it would be pretty useless. In fact, there's another, more common way for binding to occur: the "dot parentheses" syntax. It looks like this:
var s = "one,two,three";
var s2 = s.split(',');
That syntax says exactly the same thing as:
var s = "something";
var f = s.split; // get a reference to the function
var s2 = f.call( s, ',' ); // invoke f with s as the scope
What's going on here? When you use a "." dot to look up the attribute of an object, that object will be used as the scope if you then immediately call the attribute as a function. If there are multiple dots, then the rightmost object is used:
var obj = {
name: 'outer',
inner: { name: 'inner' }
};
obj.inner.name.toUpperCase();
If you don't immediately call the function, then the binding is lost:
var s = "one,two,three";
var split = s.split;
alert( split(',') ); // this is undefined or the Window object.
And you'll get the default binding, which is undefined
or the global Window
object.
You don't have to use a dot; the equivalent square bracket syntax binds the same way.
"Dot parentheses" is by far the most common way to bind a function to a scope — the this
keyword and the concept of scope exists solely to enable this syntax. By design, it looks and behaves like the method invocation syntaxes of other popular languages.
new
is magical, too
Another way to define the scope of a function call is to put the new
operator in front of the call:
function returnThis() { return this; }
var x = returnThis(); // x is the global window
var obj = new returnThis(); // obj is a new, empty Object.
This creates a new, fresh object and passes it is as this
. The new
operator has a couple of other interesting effects:
- the object it creates inherits from the function's prototype
- the return value of the expression will be the new Object (unless the function returns a value.)
For now though, just be aware that prefacing a function with the new
operator is another way do define this
.
Writing methods
In JavaScript, a method is simply a function that's set as the attribute of an object and written to make use of this
. You can assume that the method with be called with "dot parentheses" and bound to the base object, or that the caller will use .call()
to achieve the same thing. Likewise, when you invoke a method, you are responsible for binding it to the correct scope.
Simulating classes in JavaScript is outside the scope of this tutorial, so I'll use a super-simple example to show what user-defined methods look like:
var counter = {
count: 0,
total: 0
};
counter.collect = function(x) {
this.count++;
this.total += x;
return this;
};
counter.getAverage = function(x) {
return this.total / this.count;
};
This object accepts a series of data-points through the collect()
method, and provides an average via the getAverage()
method. Note:
-
We can do anything we want to the object via
this
: we can access, set, delete, or mutate any attribute. -
We can't use the
function f(x,y)
syntax when defining methods; instead, assign an attribute of the object to an anonymous function, as shown. -
if you don't have anything else to return, return
this
from methods. This enables method chaining (see below) which is very convenient.
The counter
object can be used like so:
counter.collect(1); // "dot parentheses"
counter.collect.call(counter, 2); // .call() method
counter.collect(3).collect(4).collect(5); //method chaining
alert( counter.getAverage() );
Note that method chaining is only possible with collect()
because that method return a reference to this
, which we can then invoke another method on.
Use a closure to permanently bind to a scope
When we call a method, we are responsible for binding that method to its scope. That requires us to actually have a reference to the scope, which can get lost if we start passing functions around. Suppose we try to count clicks on an element this way:
var clickCounter = {
count: 0,
increment: function() {
this.count++;
}
};
el.addEventListener( 'click', increment ); //wrong!
increment()
gets called when a click occurs, but it doesn't get bound to the correct scope. We need to use a closure to force the binding:
function incrementClickCounter() {
clickCounter.increment();
}
el.addEventListener( 'click', incrementClickCounter );
Or equivalently but more concisely:
el.addEventListener('click', function() { clickCounter.increment(); });
Accept an optional scope for callbacks
Using a closure is the only general way to pass a method as a function. However, it's a common idiom for functions that accept a callback to also accept an optional scope argument. The function then promises to invoke the callback with .call(scope)
.
For functions that support this idiom, you can simply pass the scope in:
var listeners = [];
var scopes = [];
function addListener( listener, scope ) {
listeners.push( listener );
scopes.push( scope );
}
function fireListeners() {
for( var i=0; i < listeners.length; i++ ) {
listeners[i].call( scope );
}
}
addListener( clickCounter.increment, clickCounter );
If the scope isn't passed in, it defaults to undefined
and everything works fine for normal functions — the scope is optional. When you write functions that accept callback function references, you can make things a little easier for your clients by using this idiom.
Create an alias for this
when using nested functions
this
is a keyword, not a variable. It only refers to the scope of the closest enclosing function, which can be confusing when dealing with nested functions:
page.buildGetTitle = function() {
function getTitle() {
return this.application + ' - ' + this.name;
}
return getTitle;
}
This code won't work, because this
refers to the scope of getTitle()
, not page
as intended. Instead, simply give this
a local alias at the beginning of the function and use that instead:
page.buildGetTitle = function() {
var page = this;
function getTitle() {
return page.application + ' - ' + page.name;
}
return getTitle;
}
Be doubly sure to var
the alias variable. Something horrible will happen if you don't: the code will work. It won't break until later, when you have multiple getTitle()
functions floating around. And it won't always be obvious that it's broken, either. Avoid such insidious bugs by always double-checking that your closures are not accidentally refering to global variables.
I've found that it can be very confusing to sometimes use this
and sometimes use the alias. I recommend only using the alias if it's defined.
Use .apply()
to write generic wrappers
.apply()
is a Function method similar to .call()
but more flexible. .call()
accepts any number of arguments and passes them all through to the function starting with the second one. .apply()
accepts exactly two arguments: the scope and an array of arguments:
var slice = "".slice;
var test = slice.call( "testing", 0, 4 );
// is equivalent to
var test = slice.apply( "testing", [0, 4] );
.call()
is preferable, unless you don't know how many arguments you want to pass in.
The second argument of apply can be the arguments
collection instead of an Array, since arguments
is an Array-like object. If we also pass this
in as the first argument then the bind the function to the same scope, too. .apply(this, arguments)
says, "call this functions with the same scope and arguments that were passed into me."
Lets see how that can be useful. trivialWrapper()
accepts a function and returns another function that does exactly the same thing:
function trivialWrapper( func ) {
return function() {
return func.apply( this, arguments );
}
}
Do you see why the returned function has identical behavior to the original, for all possible functions, arguments, and bindings?
Now that we can preserve 100% of the behavior of a function, we have a starting point for adding behavior to arbitrary functions. Here are some examples to get you thinking about the possibilities:
// alert when entering and exiting the function
function debugWrapper( func ) {
return function() {
alert( 'debug called with: ' + arguments );
var ret = func.call( this, arguments );
alert( 'debug returned: ' + ret );
return ret;
}
}
// give a function the no-throw guarantee by returning null on error
function noThrowWrapper( func ) {
return function() {
try {
return func.apply( this, arguments );
catch(e) {
return null;
}
}
}
// invert the logic of a predicate
// (a predicate is any function returning a boolean)
function notWrapper( predicate ) {
return function() {
return !func.apply( this, arguments );
}
}
This can be a powerful dynamic programming technique; google "aspect-oriented programming" and "decorators" to learn more.
Check your JavaScript framework for convenience functions
In this day and age, you're probably using a JavaScript library such as YUI, ExtJS, or PrototypeJS. Most libraries provide functions to help with the issues I've discussed here. Don't get me wrong; you still need to have know everything I've discussed here by heart to write JavaScript at a high level. However, once you understand it, you may come to appreciate the utilities provided by your library.
For example, PrototypeJS has an useful little method for binding functions to scopes which is somewhat more concise than the closure technique I showed you above.
Remember:
-
Scope is a hidden argument passed into functions as
this
- Dot parentheses is magic
- Methods are just object attributes of type function
- Use a closure to permanently bind to a scope
- Accept an optional scope for callbacks
-
Create an alias for
this
when using nested functions -
Use
.apply()
to write generic wrappers
- Oran Looney May 21st 2008
Thanks for reading. This blog is in "archive" mode and comments and RSS feed are disabled. We appologize for the inconvenience.