JavaScript this keyword
📅 March 31st, 2020
⏱️ 7 min read
Updated: June 17th, 2020
This
A function's this
keyword behaves a little differently in JavaScript compared to other languages. It also has some differences between strict mode and non-strict mode.
Before diving into the specifics of the this
keyword in JavaScript, it’s important to know why the this
keyword exists in the first place. The this
keyword allows you to reuse functions with different contexts. Said differently, the this
keyword allows you to decide which object should be focal when invoking a function or a method. Everything we talk about after this will build upon that idea. We want to be able to reuse functions or methods in different contexts or with different objects.
Global context
In the global execution context (outside of any function), this
refers to the global
object whether in strict mode or not.
// In web browsers, the window object is also the global object:
console.log(this === window); // true
a = 28;
console.log(window.a); // 28
this.b = "U";
console.log(window.b); // "U"
console.log(b); // "U"
Function context
In most cases the value of this
differs depending on how a function is invoked (the only exception is bind
and arrow functions), so we can’t know the value of this
just by looking at the function definition, but we need to know the context in which the function is invoked and how it was invoked.
There are 5 rules to keep in mind. Let’s quickly review these rules.
Simple Function Call
this
is the global
object in non-strict mode, and undefined
in strict mode.
In the case of a simple function call, in non-strict mode this
will default to the global
object:
function f1() {
return this;
}
// in a browser
f1() === window; // true
// in node
f1() === global; // true
In the same scenario, but in strict mode, this
will be undefined
:
"use strict";
function f1() {
return this;
}
f1() === undefined; // true
Implicit Binding
this
points to the object on which the function is called (what’s to the left of the period when the function is called).
This rule will apply for the majority of cases in your day-to-day code and applies when calling a method on an object:
const cat = {
voice: "Meow",
speak: function() {
console.log(this.voice);
},
owner: {
name: "Jhon",
speak: function() {
console.log(`Hello, I'm ${this.name}`);
}
}
};
cat.speak(); // Meow
cat.owner.speak(); // Hello, I'm Jhon
Note also that we get the same result if our object only contains a reference to the function:
function speak() {
console.log(this.voice);
}
const cat = {
voice: "Meow",
speak: speak
};
cat.speak(); // Meow
Explicit Binding
We can explicitly tell the JavaScript engine to set this to point to a certain value using call
, apply
or bind
.
call
and apply
can be used to invoke a function with a specific value for this
as the first argument. The difference between them is that, call
passes in arguments one by one to the function, apply
passes in a single array that will be spread out for you as arguments to the function:
function speak(a, b) {
console.log(this.voice + a + b);
}
const cat = {
voice: "Meow"
};
speak.call(cat, 1, 2); // Meow12
speak.apply(cat, [1, 2]); // Meow12
Both call
and apply
accomplish the same task, and the first argument to both should be what this
points to. The difference is only apparent if additional arguments need to be passed to the invoked function. With call
, the additional arguments are passed as a normal comma-separated list of arguments, and with apply
an array of arguments can be passed-in.
bind
is used to create a new function that’s permanently bound to a this
value. In the following example, we create a new function that has its this permanently bound to cat
and re-assign speak
to that new permanently bound function:
function speak() {
console.log(this.voice);
}
const cat = {
voice: "Meow"
};
speak = speak.bind(cat);
speak(); // Meow
new Binding
Using the new
keyword constructs a new object, and this points to it.
When a function is invoked as a constructor
function using the new
keyword, this
points to the new object that’s created:
function Cat(name) {
this.name = name;
}
const myCat = new Cat("Kitty");
console.log(myCat.name); // Kitty
If the constructor function has a return
statement (this is rare) that returns an object
, that object
will be the result of the new
expression. Otherwise, the result of the expression is the object
currently bound to this
:
function Cat(name) {
this.name = name;
return {
name: "Another Kitty"
};
}
const myCat = new Cat("Kitty");
console.log(myCat.name); // Another Kitty
Arrow Functions
With arrow functions, this
keeps the same value as its lexical scope(parent scope).
The following example shows why we may need arrow functions:
const user = {
name: "Ahmed",
age: 28,
languages: ["JavaScript", "TypeScript"],
greet() {
const hello = `Hello, my name is ${this.name} and I know`;
const langs = this.languages.reduce(function(str, lang, i) {
// the error happens in the next line because this.languages is undefined
if (i === this.languages.length - 1) {
return `${str} and ${lang}.`;
}
return `${str} ${lang},`;
}, "");
console.log(hello + langs);
}
};
user.greet(); // Uncaught TypeError: Cannot read property 'length' of undefined
We expected this to be user
object but it's not. First, we need to look at where the function is being invoked. Wait? Where is the function being invoked? The function is being passed to .reduce
so we have no idea. We never actually see the invocation of our anonymous function since JavaScript does that itself in the implementation of .reduce
. That’s the problem. We need to specify that we want the anonymous function we pass to .reduce
to be invoked in the context of user
. That way this.languages
will reference user.languages
.
We can fix this problem in two ways:
-
Set the value of
this
explicitly usingbind
:const user = { name: "Ahmed", age: 28, languages: ["JavaScript", "TypeScript"], greet() { const hello = `Hello, my name is ${this.name} and I know`; const langs = this.languages.reduce( function(str, lang, i) { if (i === this.languages.length - 1) { return `${str} and ${lang}.`; } return `${str} ${lang},`; }.bind(this), "" ); console.log(hello + langs); } }; user.greet(); // Hello, my name is Ahmed and I know JavaScript, and TypeScript.
-
Use an arrow function as the callback function passed to
reduce
which will use the value ofthis
in its parent scope:const user = { name: "Ahmed", age: 28, languages: ["JavaScript", "TypeScript"], greet() { const hello = `Hello, my name is ${this.name} and I know`; const langs = this.languages.reduce((str, lang, i) => { if (i === this.languages.length - 1) { return `${str} and ${lang}.`; } return `${str} ${lang},`; }, ""); console.log(hello + langs); } }; user.greet(); // Hello, my name is Ahmed and I know JavaScript, and TypeScript.
Conclusion
So putting all of our rules into practice, whenever I see the this
keyword inside of a function, these are the steps I take in order to figure out what it’s referencing.
- Look to where the function was invoked.
- Was the function invoked using the
new
keyword? If so, thethis
keyword is referencing the newly created object that was made by the JavaScript interpreter. If not, continue to #3. - Was the function invoked with
call
,apply
, orbind
? If so, it’ll explicitly state what thethis
keyword is referencing. If not, continue to #4. - Is there an object to the left of the dot? If so, that’s what the
this
keyword is referencing. If not, continue to #5. - Is
this
inside of an arrow function? If so, its reference may be found lexically in the enclosing (parent) scope. If not, continue to #6. - Are you in
strict mode
? If yes, thethis
keyword isundefined
. If not, continue to #7. this
is referencing thewindow
object.