JavaScript execution context management
Hello, Habre! Today we will talk about this
because without a clear understanding of how it works this
your code can become a source of confusion and errors.
this
JS is a keyword that refers to the current execution context. Its value depends on where and how the function was called, not where it was defined.
In this article, we’ll break down all the ways to work with execution context so you can use it with confidence this
in any scenario.
Different contexts
Global context: The behavior of this outside of functions
When you are at the top level of code without functions and objects this
refers to a world object. In browsers it is window
and in Node.js – global
.
console.log(this); // В браузере выведет объект window
Here this
refers to a world object. If you use this
outside of any context, you will always get a global object.
But how will this affect the function? Consider the following example:
function showContext() {
console.log(this);
}
showContext(); // Вызовет функцию, выведет window (или global в Node.js)
Agree, this behavior can surprise, especially if you expected that this
will refer to something else. If you want to this
pointed to a specific object in the global context, you need to wrap it in another object:
const myObject = {
name: 'My Object',
showContext: showContext,
};
myObject.showContext(); // выведет myObject
Object Context: How it refers to the current object in methods
When a function is called as a method of an object, this
indicates the object to which the method belongs. This is perhaps one of the most convenient features this
.
const user = {
name: 'Polina',
greet() {
console.log(`Hello, my name is ${this.name}`);
},
};
user.greet(); // Hello, my name is Polina
In this example, when greet()
is called this
refers to an object user
and we access its properties. But what happens if we pass the method to another function?
const greetFunc = user.greet;
greetFunc(); // Hello, my name is undefined
Here this
no longer points to user
because the method was called outside the context of the object.
Functional Context: Differences between calling functions as methods and regular functions
When a function is called simply as a function (not as a method of an object), this
again points to a global object (in strict mode it will undefined
).
function showThis() {
console.log(this);
}
showThis(); // window (или undefined в strict mode)
But if you call the function as a method of the object, this
will refer to the object:
const obj = {
value: 42,
showValue() {
console.log(this.value);
},
};
obj.showValue(); // 42
Arrow functions: An explanation of why this does not change in arrow functions
Arrow functions save this
from the surrounding context. That is, this
in the arrow function will be the same as and this
in the parent function where it was declared:
const person = {
name: 'Ivan',
greet: function () {
const arrowFunc = () => {
console.log(`Hi, I'm ${this.name}`);
};
arrowFunc();
},
};
person.greet(); // Hi, I'm Ivan
Here is an arrow function arrowFunc
preserves the context this
which points to an object person
.
However, be careful: if you try to use the arrow function in object methods, it will cause this
will not refer to the object:
const obj = {
name: 'Artem',
show: () => {
console.log(this.name);
},
};
obj.show(); // undefined
Here this
refers to a global object (or undefined
in strict mode), and you will not get the expected result.
Changing the execution context
Now let’s see how to manage the execution context using methods call
, apply
and bind
. These tools allow you to customize what is referenced this
depending on your desire.
The call, apply, and bind methods: how and when to use them to control the context
Method call
allows calling a function with the specified value this
and arguments passed separately.
func.call(thisArg, arg1, arg2, ...);
const person = {
name: 'Artem',
};
function greet(greeting) {
console.log(`${greeting}, my name is ${this.name}`);
}
greet.call(person, 'Hello'); // Hello, my name is Artem
We use call
to indicate that this
in the function greet
refers to an object person
. We also pass the argument 'Hello'
that the function uses.
Method apply
works similarly call
but accepts array arguments.
func.apply(thisArg, [argsArray]);
const person = {
name: 'Nikita',
};
function greet(greeting, punctuation) {
console.log(`${greeting}, my name is ${this.name}${punctuation}`);
}
greet.apply(person, ['Hi', '!']); // Hi, my name is Nikita!
Method bind
creates a new function that will have the specified value when called this
and can even accept predefined arguments. Unlike call
and apply
, bind
does not call the function immediately, but returns a new function.
const boundFunction = func.bind(thisArg, arg1, arg2, ...);
const person = {
name: 'Charlie',
};
function greet(greeting) {
console.log(`${greeting}, my name is ${this.name}`);
}
const greetCharlie = greet.bind(person);
greetCharlie('Hey'); // Hey, my name is Charlie
Here we create a new function greetCharlie
which will always refer to the object person
.
Classes and context: how to work with this in classes
When we work with JavaScript classes, it’s important to remember that this
in class methods indicates an instance of the class. However, if you pass a class method as a callback, this
can get lost
Example:
const person = {
name: 'Dave',
};
function introduce(greeting, age) {
console.log(`${greeting}, I am ${this.name} and I am ${age} years old.`);
}
introduce.call(person, 'Hello', 30); // Hello, I am Dave and I am 30 years old.
introduce.apply(person, ['Hi', 25]); // Hi, I am Dave and I am 25 years old.
Here we use bind
in the constructor to make sure that this
always refers to an instance Person
even if the method is called outside the context of the class. If we didn’t use bind
we would get undefined
in this.name
during the call greet
through setTimeout
.
Other features
Strict Mode: Prevent unexpected values from this
Strict mode is a way to set rules that help avoid errors, simplify diagnostics, and improve code security. In strict mode, if the function is called without a context (like a normal function), this
will be undefined
rather than referencing a global object.
function showThis() {
console.log(this);
}
showThis(); // В браузере выведет window
An example with strict mode:
'use strict';
function showThis() {
console.log(this);
}
showThis(); // undefined
Object methods and that
When you define methods on objects, it is important to remember that this
inside the method, the object itself will always be referenced, even if the method is passed to another function or context. However, if you do not use bind
You may encounter unexpected results.
Example:
const obj = {
name: 'Eva',
showName() {
console.log(this.name);
},
};
const show = obj.showName;
show(); // undefined (в строгом режиме) или 'Eva' (в обычном)
Here, when we call show
, this
does not indicate obj
which leads to unexpected results.
this in event handlers
When you add an event handler, this
it usually refers to the element that the event is set to.
Example:
However, if you use an arrow function as a handler, this
will point to the parent context, not the element.
Example with an arrow function:
Implicit and explicit this
JavaScript distinguishes between implicit and explicit this
. Implicit this
is when this
is determined automatically depending on how the function was called. Apparent this
is when you use methods call
, apply
or bind
to clearly indicate what is being referred to this
.
Implicit this
:
const car = {
brand: 'Ford',
showBrand() {
console.log(this.brand);
},
};
car.showBrand(); // Ford
Apparent this
:
function showBrand() {
console.log(this.brand);
}
const myCar = { brand: 'Toyota' };
showBrand.call(myCar); // Toyota
Keep your code clear, a this
under control Remember that this
— the thing is capricious, but, like any good instrument, in skillful hands it works wonders.
OTUS experts tell more about programming languages in practical online courses. The full catalog of courses can be found at the link.