A Guide to ‘this’— One of the Least Understood Parts of JavaScript
What I wish I knew when I started learning about the ‘this’ keyword
Published on
Sep 3, 2019
Read time
8 min read
Introduction
The keyword this
is the cause of a lot of confusion in JavaScript. It’s also important enough that it’s difficult to avoid. If you use React or Angular, for example, you’ll likely to be using JavaScript classes, where the this
keyword is almost a necessity.
But even in plain old vanilla JavaScript, you may encounter this
— or its side-effects — more often than you think. For example, ES6’s so-called “fat arrow” syntax automatically adopts the this
binding from the
enclosing scope. It’s useful when creating new objects from prototypes and writing regular JavaScript functions.
The present article is intended for developers who may have used the this
keyword, but who don’t feel like they have a strong grasp of its behaviour. I hope that — if you try out the examples — you should finish the article with a much firmer idea of what this
does and why. For those who want to go further, I’ve included some resources at the bottom.
Why ‘this’ means different things at different times
this
can seem tricky because its meaning changes depending on the context in which it is used. We’ll start by looking at its most basic context.
Though this
is mostly found within objects, functions and classes, we can also call it globally, like so:
console.log(this);
Using this
without a more specific context is equivalent to calling the global object: in a web browser, that’s the window
object.
If that’s the result we want, we could also call self
, frames
or globalThis
. But these always refer to the window
object: in other words, they lack the flexibility that makes this
useful.
Not every JavaScript program runs in a browser. You should be aware that, in non-windowed environments, the global meaning of this
won’t be the window
object — because this doesn’t exist!
For example, when used globally in Node.js, this
represents an empty object:
console.log(this); // {}
this.foo = "bar";
console.log(this); // { foo: 'bar' }
Scope vs Context
When approaching this
for the first time, many developers presume that it works in a similar way to scope. But that thinking can lead to mistakes!
That’s because this
depends on context, rather than scope. In the previous section, we were talking about global context rather than global scope. As it happens, the code above had both a global context and a global scope, so what’s the difference?
What is Scope?
Scope controls the context in which variables are accessible. Here’s a classic example:
function foo() {
const bar = true;
return bar;
}
console.log(foo()); // true
console.log(bar); // ReferenceError: bar is not defined
The variable bar
is only accessible inside our function, foo
. If we call foo()
, we’ll get the value of bar
. But if we try to use bar
outside of foo
, we’ll get a ReferenceError
. This is known as local scope or functional scope.
It’s tempting to imagine that this
works in the same way. But this misunderstanding is probably the reason for most beginners’ mistakes!
What is Context?
To get an understanding of context, let’s re-write our function foo
to set the value of this.bar
:
function foo() {
this.bar = true;
return bar;
}
console.log(foo()); // true
console.log(bar); // true
Beginners often assume that, in the above example, this
refers to the function foo
. But when we try to access bar
, we can!
That’s because the expression this.bar = true
is actually writing a global variable bar
. That’s because we haven’t set a specific context and so, as in the first example in this article, this
refers to the global object. To give this
a context, you need to use a method like call
, apply
or bind
.
Defining a Context for ‘this’
We now understand that scope and context are different, but how do we define a context for this
?
Let’s begin with the simplest possible example:
function func() {
return this;
}
If we call func()
, we’ll get the global window
object like before. But there are three methods we can use to change the meaning of this
when we call it: call
, apply
and bind
.
Say we wanted this
to represent the string 'Hello World!'
, we can do that in a number of ways:
func.call("Hello World");
func.apply("Hello World");
func.bind("Hello World")();
Each line of the code above would return the string 'Hello World!'
. Like a function argument, we can use any data type with the above methods. Most commonly, you’ll likely encounter objects. Take the example below:
const a = 1;
const obj = { a: 2 };
function whatIsA() {
return this.a;
}
Whether whatIsA()
returns 1
or 2
will depend on how we call it:
whatIsA(); // 1
whatIsA(obj); // 1
whatIsA.call(obj); // 2
whatIsA.apply(obj); // 2
whatIsA.bind(obj)(); // 2
Notice that passing obj
as an argument of whatIsA
has no effect on the result of this
.
So Why Bother?!
At this point, you might be wondering why we should go to all the extra effort. Wouldn’t a regular function argument work fine?
Well, in simple examples like the ones above, regular arguments would be fine! The usefulness of this
becomes more apparent when we start chaining functions to one another:
function round() {
return (this.price / 100).toFixed(2);
}
function toCurrency() {
return "$" + round.call(this);
}
const sale = {
price: 1299,
};
const sale2 = {
price: 3750,
};
console.log(toCurrency.call(sale)); // $12.99
console.log(toCurrency.call(sale2)); // $37.50
In the code above, we are converting integers to dollars in two steps. First, we round the number to two decimal places. Then we add the dollar symbol.
Of course, the desired effect could also be achieved using arguments:
function round(obj) {
return (obj.price / 100).toFixed(2);
}
function toCurrency(obj) {
return "$" + round(obj);
}
But, as the number or complexity of our functions increases, it can quickly become difficult to keep track of every argument and make sure it’s in the correct form.
By contrast, when using this
, we can be sure of the exact form our context will be taking: it will always be exactly whatever we pass into our call
or apply
method.
Call and Apply
The difference between call
and apply
is simply semantic: call()
accepts an argument list, while apply()
accepts a single array of arguments.
To demonstrate, let’s extend our toCurrency
function so that it allows different currencies and gives the option to change the dot to a comma, so we can get results like €12,99
.
function round() {
return (this.price / 100).toFixed(2);
}
function toCurrency(code, separator) {
let symbol;
let number = round.call(this);
switch (code) {
case "EUR":
symbol = "€";
break;
case "JPY":
symbol = "¥";
break;
case "USD":
default:
symbol = "$";
break;
}
if (separator) {
number = number.toString().replace(".", separator);
}
return symbol + number;
}
If we want to display a price in Euros, with a comma as a separator instead of a dot, we could either use:
toCurrency.call(sale, "EUR", ","); // €12,99
Or we could use:
toCurrency.apply(sale, ["EUR", ","]); // €12,99
Bind
Up to this point, whenever we want to use a specific context, we’ve had to state it at the point we call our function. But what if we want to associate a function to a specific context, so that our chosen context is presumed every time we call the function? This is where bind
comes in!
const o = {
x: 10,
getX: function () {
return this.x;
},
};
let y = o.getX;
If we call y
, what’s will the result be? Without any specific context, this
will take the value of x
in the global context. In this case, there isn’t one, so we’ll get undefined
.
But we can bind our function y
to the context of o
:
const o = {
x: 10,
getX: function () {
return this.x;
},
};
let y = o.getX;
y = y.bind(o);
Now, whenever we call y
, we’ll get the number 10
. Pretty useful!
Using Bind in Class Methods
If you’re used to a framework like React, which makes heavy use of JavaScript classes, you’ll likely be used to binding your methods inside your class’s constructor
method:
import React from "react";
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.addOne = this.addOne.bind(this);
}
addOne(event) {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<button type="button" onClick={this.addOne}>
Click Me
</button>
);
}
}
The reason for the longwinded expression this.addOne = this.addOne.bind(this)
is that we always want the addOne
method to be executed in the context of the current React class. Otherwise, our value of this
will not be what we expect. React classes run in ‘strict mode’, so instead of this
defaulting to window
, it returnsundefined
: our addOne
method would attempt to use undefined.setState
, which — of course — doesn’t exist!
Using the bind
method means that, whenever we call addOne
, it will use the setState
method that is part of our class.
Implicit Binding with Arrow Functions
More experienced React developers might prefer to avoid writing out the bind
expression with every new method. Instead, the Counter
component above could be re-written like so:
import React from "react";
class Counter extends React.Component {
state = { count: 0 };
addOne = (event) => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<button type="button" onClick={this.addOne}>
Click Me
</button>
);
}
}
This works because the arrow function automatically adopts the this
binding from the enclosing scope — in this class, Counter
.
this in Objects
Lastly, it’s worth noting that when this
is used in an object method, its context — unless otherwise defined — will be that of the most immediate object.
function func() {
return this;
}
let obj = { func, a: 10 };
Take the code above. If we call func()
, we’ll get the global object. But if we call obj.func()
, we’ll get our obj
itself.
Puzzles
If you’d like to test your understanding of this
, here are 3 short puzzles. All of these should work if you paste them directly into your browser’s console, so I recommend trying them out for yourself once you’ve had a go at the answer.
Puzzle 1: Scope and Context
Now that you have a sense of the difference between scope and context, here’s a puzzle for you. What number will calling doThing
return?
const a = 1;
function doThing() {
const a = 2;
this.doIt = function () {
return a;
};
return doIt();
}
Puzzle 2: Implicit Binding with Arrow Functions
Given that arrow functions automatically adopt the this
binding from the enclosing scope, what do you think will happen when we call obj.func()
below?
const func = () => {
return this;
};
let obj = { func, a: 10 };
Puzzle 3: Nested Objects
Based on the code below, if we call outerObj.innerObj.val
, what will we get?
this.val = 10;
const outerObj = {
val: 50,
innerObj: {
val: 100,
func: function () {
return this.val;
},
},
};
const func = outerObj.innerObj.func;
Answer 1
The answer is 2
. doIt
is defined globally, but because of where it is defined (within the functional scope of doThing
) it has access to var a = 2
!
This might seem confusing at first, but once we’ve recognised that scope and context are different, it should make sense!
Answer 2
We get the global window
object!
This cannot be changed by using call
, apply
or bind
. If you need to change a binding, use a regular function.
Answer 3
The context of this
is our innerObj
, so the val
we get is 100
.
If we wanted to get 50
, we would have to change our context to the outerObj
by calling outerObj.innerObj.func.call(outerObj)
.
And if we wanted to get 10
, we could call outObj.innerObj.func.call(this)
.
Further Reading
That’s the end of our whistle-stop tour of the this
keyword. I hoped this article helped clarify what this
can do, what it isn’t, and where it can be useful.
If you’d like some further examples of this
in action, or you want to dig a little deeper, I recommend the following.
1. Mozilla Developer Network, this
As ever, MDN is an extremely useful resource, with lots of examples of this
in action.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
2. Todd Motto, Understanding the this
keyword in JavaScript
There are lots of articles about this
on the web, but this one jumped out at me for its simple and clear examples!
https://ultimatecourses.com/blog/understanding-the-this-keyw...
3. Kyle Simpson, You Don’t Know JS — this
& Object Prototypes
https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/this-...
Related articles
You might also enjoy...
How to Automate Merge Requests with Node.js and Jira
A quick guide to speed up your MR or PR workflow with a simple Node.js script
7 min read
Automate Your Release Notes with AI
How to save time every week using GitLab, OpenAI, and Node.js
11 min read
How to Create a Super Minimal MDX Writing Experience
Learn to create a custom MDX experience so you can focus on writing without worrying about boilerplate or repetition
12 min read