managing execution context in JavaScript
In this article we will look at all the ways to work with execution context so that you can confidently use this
in any scenario.
Different contexts this
Global context: this behavior outside functions
When you are at the top level of code – without functions and objects – this
refers to a global object. In browsers this is window
and in Node.js – global
.
console.log(this); // В браузере выведет объект window
Here this
refers to a global object. If you are using this
outside of any context, you will always get a global object.
But how will this affect the functions? Consider the following example:
function showContext() {
console.log(this);
}
showContext(); // Вызовет функцию, выведет window (или global в Node.js)
Agree, this behavior may surprise you, 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 this refers to the current object in methods
When a function is called as a method of an object, this
points to 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()
called, this
refers to an object user
and we get access to 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 indicates user
because the method was called outside the object's context.
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 the global object (in strict mode this would be undefined
).
function showThis() {
console.log(this);
}
showThis(); // window (или undefined в strict mode)
But if you call a function as a method of an object, this
will refer to the object:
const obj = {
value: 42,
showValue() {
console.log(this.value);
},
};
obj.showValue(); // 42
Arrow Functions: Explaining Why This Doesn't Change in Arrow Functions
Arrow functions save this
from the surrounding context. That is this
in an arrow function will be the same as 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's an arrow function arrowFunc
preserves context this
which points to an object person
.
However, be careful: if you try to use an 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.
Call, apply and bind methods: how and when to use them to manage context
Method call
allows you to call a function with a 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 function greet
refers to an object person
. We also pass the argument 'Hello'
which the function uses.
Method apply
works similarly call
but takes arguments as an array.
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, when called, will have the given value this
and can also take 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 classes in JavaScript, it is important to remember that this
in class methods, points to an instance of the class. However, if you pass a class method as a callback, this
may 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 class context. If we didn't use bind
we would get undefined
V this.name
when calling greet
through setTimeout
.
Other features
Strict mode: preventing unexpected this values
Strict mode is a way to define rules that help avoid errors, simplify diagnostics, and improve code security. In strict mode, if a function is called without context (like a normal function), this
will undefined
rather than referencing the global object.
function showThis() {
console.log(this);
}
showThis(); // В браузере выведет window
Example with strict mode:
'use strict';
function showThis() {
console.log(this);
}
showThis(); // undefined
Object methods and this
When you define methods on objects, it is important to remember that this
inside a method will always refer to the object itself, even if the method is passed to another function or context. However, if you don't use bind
you may experience 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 on which the event is set.
Example:
<button id="myButton">Click me</button>
<script>
document.getElementById('myButton').addEventListener('click', function() {
console.log(this); // будет ссылаться на <button>
});
</script>
However, if you use the arrow function as a handler, this
will point to the parent context, not the element.
Example with an arrow function:
<button id="myButton">Click me</button>
<script>
document.getElementById('myButton').addEventListener('click', () => {
console.log(this); // будет ссылаться на родительский контекст, не на <button>
});
</script>
Implicit and explicit this
JavaScript distinguishes between implicit and explicit this
. Implicit this
– this is when this
is determined automatically depending on how the function was called. Explicit this
– this is when you use methods call
, apply
or bind
to explicitly indicate what is being referenced this
.
Implicit this
:
const car = {
brand: 'Ford',
showBrand() {
console.log(this.brand);
},
};
car.showBrand(); // Ford
Explicit this
:
function showBrand() {
console.log(this.brand);
}
const myCar = { brand: 'Toyota' };
showBrand.call(myCar); // Toyota
Keep your code clear and this
under control. Remember that this
– a capricious thing, but like any good instrument, it works wonders in the right hands.
OTUS experts talk more about programming languages in practical online courses. With a complete catalog of courses you can check out the link.