The this
keyword is a double-edged sword—it can be a source of complicated errors—and it can also make life easier for you as a developer once you know how it really works. These days, I feel that community pushes the language to more functional paradigms. We are not using the this
keyword so much. And I bet it still confuses people because its meaning is different based on context.
So, in this article, the this
keyword will be explained to give you a good understanding of how it really works.
INTRODUCTION
This article is for every Javascript developer. You will learn the following:
- What the
this
keyword is in Javascript? - What the
this
keyword represents in Node. - How the
this
keyword is determined in the Global and Function Execution Context. - Different ways a function is invoked and how it relates to
this
- How to control the value of
this
usingcall()
andapply()
methods. - How to use the
bind()
method. - How
this
behaves in an arrow function
WHAT IS THE this
KEYWORD
The this
keyword is a variable used to store an object reference when a function is invoked in Javascript. The object the this
keyword reference or points to depends on the context this
is used in. Conceptually, this
is akin to a pronoun in the English language, because it is used to refer to an object—just as a pronoun refers to a noun.
For example: “Mary is running fast because she is trying to catch the bus.”
In the above statement the pronoun "she" is used to refer to the antecedent(antecedent is the noun that a pronoun refers to) "Mary". Let's relate this concept to the this
keyword.
const person = {
name: "Mary",
pronoun: "she",
Activity: function () {
// this = person
console.log(`${person.name} is running fast because ${this.pronoun} is trying to catch the bus`)
}
}
person.Activity() // Mary is running fast because she is trying to catch the bus
In the preceding code this
is used as a reference value of the person
object, just like how the pronoun "she" is used to refer to "Mary".
HOW IS THE VALUE OF this
DETERMINED
The this
keyword is determined by the Execution context it is used in. In a nutshell, the Execution context is an abstract environment used by Javascript for code execution, it is of three variants:
- Global Execution Context
- Function Execution Context
There is also the eval()
Execution Context but it is rarely used due its malicious nature. Read this article to learn more about the Execution Context.
Global Execution Context
In the Global Execution Context, the this
keyword references the global object, which is the window
object on the web browser.
console.log(window === this ) // true
this.color = 'Green'
console.log(window.color) // Green
In the preceding code, a property is added to the global window
object through the this
keyword.
N/B: In the Global Execution Context the this
keyword will always reference the global object regardless if Javascript is in strict mode or non-strict mode.
this
keyword in Node
As per NodeJS documentation
In browsers, the top-level scope is the global scope. That means that in browsers if you're in the global scope var something will define a global variable. In Node.js this is different. The top-level scope is not the global scope; var something inside a Node.js module will be local to that module.
What the above statement means is that the this
keyword does not reference the global object in NodeJS. Instead, it points to the current module it is been used in—i.e the object exported via module.exports
.
For example, let's look at a hypothetical module called app.js
┣ 📄 app.js
console.log(this);
module.exports.color = 'Green';
console.log(this);
output:
┣ $ node app.js
{}
{color: 'Green'}
In the preceding code, first, an empty object is logged because there are no values in module.exports
in the app.js
module. Then the color
property is added to the module.exports
object, when this
is logged again an updated module.exports
object is returned with a color
property.
How to access the global object in Node
We now know that the this
keyword doesn't reference the global object in Node as it does in the browser. In Node the global object is accessed using the global
keyword, irrespective of where the global
keyword is used.
┣ 📄 app.js
console.log(global);
output:
┣ $ node app.js
// logs the node global object
The global object exposes a variety of useful properties about the Node environment.
Function Execution Context
In the Function Execution Context how the this
keyword is determined depends on how a function is called of invoked.
A Javascript function can be invoked in four ways:
- Invocation as a function
- Invocation as a method
- Invocation as a constructor
- Invocation with the apply and call methods
Invocation as a function
When a function is invoked as function(i.e when a function is called using the ()
operator), this
references the global window
object in non-strict mode and is set to undefined
in strict mode.
Example
function A() {
console.log(this === window) // true
}
function B() {
"use strict"
console.log(this === window) // false
}
function C() {
"use strict"
console.log(this === undefined) // true
}
A(); // true
B(); // false
C(); // true
Invocation as a method
When a function is invoked as a method(i.e via an object property), this
references the method's "owning" object.
Example
let Nigeria = {
continent: 'Africa',
getContinent: function () {
return this.continent;
}
}
console.log(Nigeria.getContinent()); // Africa
Invocation as a constructor
To invoke a function as a constructor, we precede the function invocation with the new
operator.
Example
function Context() {return this; }
new Context();
When a function is invoked as a constructor(via the new
operator) a couple of special actions take place:
- A new empty object is created
- This object is passed to the constructor as the
this
reference object—i.e the objectthis
will point to when the function is invoked. - The newly constructed object is returned as the
new
operator's value.
Example
function Person() {
this.name = 'Mary',
this.age = 20
}
const person1 = new Person();
console.log(person1.age) // 20
The preceding code and diagram show and explains how this
will reference an object when a function is invoked as a constructor.
If you try to invoke a constructor function without the new
operator, this
will point to undefined
, not an object.
Example
function Person() {
this.name = 'Mary',
this.age = 20
}
const person2 = Person();
console.log(person2.age)
// // => TypeError: Cannot read property 'age' of undefined
To make sure that the Person()
function is always invoked using constructor invocation, you add a check at the beginning of the Person()
function:
function Person() {
if (!(this instanceof Person)) {
throw Error('Must use the new operator to call the function');
}
this.name = 'Mary',
this.age = 20
}
const person2 = Person();
console.log(person2.age)
// // => Must use the new operator to call the function
ES6 introduced a meta-property named new.target
that allows you to detect whether a function is invoked as a simple invocation or as a constructor.
You can modify the Person()
function to use the new.target
metaproperty:
function Person() {
if (!new.target) {
throw Error('Must use the new operator to call the function');
}
this.name = 'Mary',
this.age = 20
}
const person2 = Person();
console.log(person2.age)
// // => Must use the new operator to call the function
Invocation with call and apply methods (Indirect Invocation)
Functions are objects, and like all Javascript objects, they have methods. Two of these methods, call()
and apply()
, invoke the function indirectly. Both methods allow you to specify explicitly the this
value(object reference) for the invocation, which means you can invoke any function as a method of any object, even if it is literally not a method of that object. call()
and apply()
also allow you to specify the arguments for the invocation. The call()
method uses its own argument list as arguments to the function, and the apply()
method expects an array of values to be used as arguments. In both call()
and apply()
the first argument is the this
keyword—which represents the object on which the function is to be invoked.
Example
function getContinent(prefix) {
console.log(`${prefix} ${this.continent}`);
}
let nigeria = {
continent: 'Africa'
};
let china = {
continent: 'Asia'
};
getContinent.call(nigeria, "Nigeria is in");
getContinent.call(china, "China is in");
Output:
Nigeria is in Africa
China is in Asia
In this example, we called the getContinent()
function indirectly using the call()
method of the getContinent()
function. We passed nigeria
and china
object as the first argument of the call()
method, therefore, we got the corresponding country's continent in each call.
The apply()
method is similar to the call()
method, but as you already know—its second argument is an array of arguments.
getContinent.apply(nigeria, ["Nigeria is in"]);
getContinent.apply(china, ["China is in"]);
Output:
Nigeria is in Africa
China is in Asia
Arrow functions
In arrow functions, JavaScript sets the this
lexically. This means the value of this
inside an arrow function is defined by the closest containing "non-arrow" function. In addition, the value of this
inside an arrow function can't be changed. It stays the same throughout the entire life cycle of the function.
Let's look at some examples:
let getThis = () => this;
console.log(getThis() === window); // true
In this example, the this
value is set to the global object i.e., the window object in the web browser. Let's understand the preceding code more with the help of the stack and heap.
- The
getThis
arrow function is lexically scoped to theGlobal()
"non-arrow" function that returns globalwindow
object. - The
this
value in thegetThis
arrow function is the globalwindow
object since thethis
value of the function it is lexically scoped to point to this object.
Let's look at another example:
function confirmThis () {
let getThis = () => this;
console.log(getThis() === window); // true
}
confirmThis();
Output:
true
Since the this
value of a "normal" function points to the global window
object in "non-strict mode". The this
value will also point to the window
object since it is lexically scoped to the confirmThis()
function. However, in "strict" mode the case is different.
function confirmThis () {
"use strict"
let getThis = () => this;
console.log(getThis() === window); // true
}
confirmThis();
Output:
false
The this
value of the confirmThis()
function will be undefined
in strict mode, same applies to the getThis
arrow function.
Using the bind method
As per MDN
The
bind()
method creates a new function that, when called, has itsthis
keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
Example
const module = {
x: 42,
getX: function() {
return this.x;
}
};
const unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// expected output: undefined
const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// expected output: 42
In the preceding code the module
object method getX()
is invoked as a "function"(instead of a method of module
) in the global scope. This causes it to lose its this
reference to the module
object. For this
to still point the module
object when the getX
method is invoked as a "function" instead of a "method", it needs to be "bound" to the module
object via the bind()
method—const boundGetX = unboundGetX.bind(module);
.
CONCLUSION
Now you know how this
keyword works and the different contexts it applies. You should be able to use it comfortably when required.
Summary
In this article you learned the following:
- What the
this
keyword is in Javascript? - What the
this
keyword represents in Node. - How the
this
keyword is determined in the Global and Function Execution Context. - Different ways a function is invoked and how it relates to
this
- How to control the value of
this
usingcall()
andapply()
methods. - How to use the
bind()
method. - How
this
behaves in an arrow function
GLOSSARY
Stack or CallStack: A stack is a data structure that follows the Last in First Out(LIFO) principle. However, the execution stack is a stack that keeps track of all the execution context created during code execution. The stack also stores static data in Javascript(variables and reference values). Learn more here
Heap: The heap is a data structure used for storing dynamic data in Javascript. This is where all Javascript objects are stored. Learn more here.
Lexical scope: Read this stack overflow answer for better understanding.
Javascript strict mode: MDN
A TIP FROM THE EDITOR: For more on JavaScript’s internal details, don’t miss other articles by the same author: JavaScript Types and Values, explained, JavaScript type conversions explained, and Explaining JavaScript’s Execution Context and Stack.