4 Ways to Preserve Execution Context in JavaScript

Joseph Castrigno
3 min readMar 1, 2020

--

First Example is below. We lost execution context when we assigned a method to a variable. In the output, our cat is named undefined instead of the desired Tito. How do we preserve our desired execution context?

let animal = {
cat: {
name: 'Tito',
meow: function() {
console.log(`my name is ${this.name} and I'm meowing. Meow!`);
}
}
}
let ourMeowFunc = animal.cat.meow;
ourMeowFunc();

Solution: we can create an external function to pass our variable to.

function callFunction(func, context) {
func.call(context);
}
let animal = {
cat: {
name: 'Tito',
meow: function() {
console.log(`my name is ${this.name} and I'm meowing. Meow!`);
}
}
}
let ourMeowFunc = animal.cat.meow;
callFunction(ourMeowFunc, animal.cat);

However we need to provide the execution context so it isn’t invoked simply as a function, and invoke the function in callFunction using .call() instead of as a regular function. We need to specify that the execution context is the object referenced by the cat property of the animal object.

Alternatively, we can use .bind() to create a new function with the desired execution context but we must remember that .bind() returns a new function so we’ll have to assign it to a variable, then execute it using that variable as a handle:

function callFunction(func, context) {
func.call(context);
}
let animal = {
cat: {
name: 'Tito',
meow: function() {
console.log(`my name is ${this.name} and I'm meowing. Meow!`);
}
}
}
let ourMeowFunc = animal.cat.meow.bind(animal.cat);callFunction(ourMeowFunc, animal.cat);

Sometimes we want to execute a function inside of a method:

let athlete = {
name: 'Joe',
run: function() {
function running() {
console.log(`${this.name} is running`);
}
running();
}
}
athlete.run();

This outputs undefined instead of the desired name property because running() is invoked as a regular function within the run method, so the execution context is the global object.

We can use another solution: create a local variable within the run function definition and assign it to this:

let athlete = {
name: 'Joe',
run: function() {
let self = this;
function running() {
console.log(`${self.name} is running`);
}
running();
}
}
athlete.run();

The reason this works is because running() has access to the local variable self which holds the value this. The value this held by self is the object athlete.

Another example of this is below:

let obj = {
type: 'round'
}
function goRound() {
let self = obj;
function going() {
console.log(self.type);
}
going();
}
goRound();

We can also use .call() to provide an explicit execution context:

let obj = {
type: 'round'
}
function goRound() {
function going() {
console.log(this.type);
}
going.call(obj);
}
goRound();

We have the option of using .bind() too, and we can execute the variable we assign the function to as a regular function because the execution context is being set permanently:

let obj = {
type: 'round'
}
function goRound() {
function going() {
console.log(this.type);
}
let endFunc = going.bind(obj);
endFunc()
}
goRound();

Note that we can’t use .bind() on the end of the going() function declaration. .bind() returns a new function so it must be called on an existing function.

Lastly, an arrow function is an easy and commonly used way to preserve execution context:

let obj = {
type: 'round',
goRound: function () {
let going = () => {
console.log(this.type);
}
going();
}
}
obj.goRound();

Arrow functions inherit their execution context from the surrounding scope. In this case, when determining the value of this, it does matter where the function is defined.

--

--