Hi, I'm Nick. This is my blog. I'm a life-long unschooler living in New York. You can find more about me here.
I help run the Recurse Center (YC'S10).
Follow me @nicholasbs
Many people get tripped up by the this keyword in JavaScript. I think the
confusion comes from people reasonably expecting this to work like “this”
does in Java or the way people use “self” in Python. Although this is
sometimes used to similar effect, it’s nothing like “this” in Java or other
languages. And while it’s a little harder to understand, its behavior isn’t
magic. In fact, this follows a relatively small set of simple rules. This
post is an explanation of those rules.
But first, I want to give some terminology and information about JavaScript
runtime environments which will hopefully help you develop a mental model to
better understand this.[1]
Every line of JavaScript code is run in an “execution context.” The JavaScript runtime environment maintains a stack of these contexts, and the top execution context on this stack is the one that’s actively running.
There are three types of executable code: Global code, function code, and
eval code. Roughly speaking, global code is code at the top level of your
program that’s not inside any functions, function code is code that’s inside
the body of a function, and eval code is global code evaluated by a call to
eval.
The object that this refers to is redetermined every time control enters
a new execution context and remains fixed until control shifts to a different
context. The value of this is dependent upon two things: The type of code
being executed (i.e., global, function, or eval) and the caller of that code.
All JavaScript runtimes have a unique object called the global object. Its
properties include built-in objects like Math and String, as well as extra
properties provided by the host environment.
In browsers, the global object is the window object. In Node.js, it’s just
called the “global object.” (I’m going to assume you’re running code in
a browser, however, everything I say should apply to Node.js as well.)
The first rule is simple: this refers to the global object in all global
code. Since all programs start by executing global code, and this is fixed
inside of a given execution context, we know that, by default, this is the
global object.
What happens when control shifts to a new execution context? There are only
three cases where the value of this changes: method invocations, functions
called with the new operator, and functions called using call and apply.
I’ll explain each of these in turn.
If we call a function as a property of an object using either dot (i.e.,
obj.foo()) or bracket (i.e., obj["foo"]()) notation, this will refer to
the parent object in the body of the function:
var counter = {
val: 0,
increment: function () {
this.val += 1;
}
};
counter.increment();
console.log(counter.val); // 1
counter['increment']();
console.log(counter.val); // 2This is our second rule: this refers to the parent object inside function
code if the function is called as a property of the parent.
Note that if we call the same function directly, that is, not as a property of
the parent object, this does not refer to the counter object:
var inc = counter.increment;
inc();
console.log(counter.val); // 2
console.log(val); // NaN Here, this was not changed when inc was called, so it still referred to the
global object. When inc ran
this.val += 1;it was effectively running:
window.val += 1;window.val is undefined, and adding 1 to undefined yields NaN.
Any JavaScript function can be used as a constructor function with new. The
new operator creates a new object and sets this to the new object inside
the function it was called with. For example:
function F (v) {
this.val = v;
}
var f = new F("Woohoo!");
console.log(f.val); // Woohoo!
console.log(val); // ReferenceErrorThis leads to our third rule: this in function code invoked using the new
operator refers to the newly created object.
Note that there’s nothing special about F. If we call it without using new,
this will refer to the global object:
var f = F("Oops!");
console.log(f.val); // undefined
console.log(val); // Oops!In this case, F("Oops!") is a regular function call, and this doesn’t
get set to a new object, because no new object is created since the new
operator isn’t used. this remains set to the global object.
All JavaScript functions have two methods, call and apply, which let you
call functions and explicitly set the value of this. The apply method takes
two arguments: an object to set this to, and an (optional) array of arguments
to pass to the function:
var add = function (x, y) {
this.val = x + y;
},
obj = {
val: 0
};
add.apply(obj, [2, 8]);
console.log(obj.val); // 10The call method works exactly the same as apply, but you pass the arguments
individually rather than in an array:
add.call(obj, 2, 8);
console.log(obj.val); // 10This is our fourth rule: this is set to the first argument passed to call
or apply inside function code when that function is called with either call
or apply.
That’s it! You can figure out what object this refers to by following a few simple rules:
this refers to the global object.this refers to
the parent object inside that function.new operator, this refers to the
newly created object inside that function.call or apply, this refers to the
first argument passed to call or apply. If the first argument is null
or not an object, this refers to the global object.If you understand and follow those four rules, you will always know what this is.
Remember when I said that code evaluated inside eval is its own type of
executable code? Well, that’s true, and it means that the rules for determining
what this refers to inside of eval code are a little more complex.
As a first pass, you might think that this directly inside eval refers to the
same object as it does in eval’s caller’s context. For example:
var obj = {
val: 0,
func: function() {
eval("console.log(this.val)");
}
};
obj.func(); // 0That works likely as you expect it to. However, there are many cases with
eval where this will probably not work as you expect:
eval.call({val: 0}, "console.log(this.val)"); // depends on browserThe output of the above code depends on your browser. If your JavaScript
runtime implements ECMAScript
5,
this will refer to the global object and the above should print undefined,
because it’s an “indirect” call of eval. That’s what the latest versions of
Chrome and Firefox do. Safari 5.1 actually throws an error (“The ‘this’ value
passed to eval must be the global object from which eval originated”), which
is kosher according to ECMAScript
3.
If you want know how this works with eval, you should read Juriy Zaytsev’s
excellent, “Global eval. What are the
options?” You’ll
learn more about eval, execution contexts, and direct vs. indirect calls than
you probably ever wanted to know.
In general, you should just avoid using eval, in which case the
simple rules about this given previously will always hold true.
This is a somewhat simplified view of things to make developing a basic mental model easier. For a more detailed explanation, read the section starting at page 37 of the ECMAScript Language Specification. I've based this post in large part off of that document, however, my understanding of it is far from perfect or complete, so please let me know (me @ nicholasbs.net) if I've misunderstood or misrepresented anything.↩