Making “this” Simple
To understand “this,” we need to check our understanding of JavaScript fundamentals. Do I know what an “execution context” and “invocation” is? As a recap, the “execution context” is the environment in which the block of code is invoked. This concept is extremely important because the value of “this” is the execution context. “Invocation” is when parentheses are used to call a function.
The value of “this” is determined by the current execution context of the function being invoked. Often, the execution context is the object that called the function. JavaScript has four function invocation types and one function definition type that affect the value of “this.” I will summarize the values of “this” then go into more detail below.
- Function Invocation | myFunction(): The execution context of functions is the global object so “this” refers to the global object.
- Method Invocation | myObject.myMethod(): A method is a function that is stored as a property of an object. As such, we invoke myMethod in the context of myObject. Consequently, “this” refers to myObject.
- Constructor Invocation |new MyClass(): When the class function is invoked, JavaScript creates a new object and sets the execution context to the new object so “this” refers to the new object.
- Explicit Invocation |myFunction.call(myObject): All functions have the .call, .apply, and .bind methods. We use these methods to specify the value of “this.”
- Arrow Function Definition |myFunction(): “this” refers to the context where the arrow function was defined.
Function Invocations: Functions are invoked in the global execution context. Therefore, when a function (not a method belonging to an object!) is invoked, “this” refers to the global object. It should be noted that myObject.myFunction is considered a method invocation instead of a function invocation.
function myFunction() {console.log(this);}myFunction(); // prints the Global Object
Method Invocations: By definition, a method is a function that is stored as a property of an object. Examples of methods are myObject.myFunction or Array.map. When we call a method with reference to the object that owns it, the method is invoked in the context of that object. In the example below, myFunction is a property of myObject. Therefore, the execution context is myObject and “this ”refers to myObject.
const myObject = { a: 1, b: 2, myFunction: function () { console.log(this); //prints myObject },};myObject.myFunction();
Let’s stop and test our knowledge of execution contexts. If we have an function inside of myFunction called innerFunction what would the value of “this” be in innerFunction? If you guessed that it refers to the global object, you’d be right. Let’s understand why “this” in innerFunction would refer to the global object. We know that myFunction is a method of myObject so when myFunction is called, the execution context is myObject. In contrast, is innerFunction a method of myFunction? No. innerFunction is simply a function that is defined inside of myFunction but it is not a property/method of myFunction. Therefore, when innerFunction is called, its execution context is the global object.
const myObject = { a: 1, b: 2, myFunction: function () { function innerFunction() { console.log(this); //prints the global object } innerFunction(); },};myObject.myFunction();
Constructor/Class Invocation: When we invoke a class using the “new” keyword, JavaScript creates a new object and sets the execution context to the new object. Now, the constructor function is called and sets the properties and methods of the new object using “this.” As you can see in the example below, the “dog” we created has two properties that were set by the constructor function { name: ‘Spot’, weight: 20 }.
class Dog { constructor(name, weight) { this.name = name; this.weight = weight; } speak() { return `${this.name} says woof.`; }}const dog = new Dog('Spot', 20);console.log(dog); // Dog { name: 'Spot', weight: 20 }console.log(dog.name); // Spotconsole.log(dog.speak()); // Spot says woof.
Explicit Invocation: When we need to specify the value of “this,” we use the call(), apply(), or bind() methods. The main difference between these methods is that bind() returns a new function whereas call() and apply() invoke the given function.
const myObject = { name: 'myName' };function myFunction(string) { console.log(string + this.name);}myFunction.call(myObject, 'Hello '); // 'Hello myName'myFunction.apply(myObject, ['Hello ']); //'Hello myName'
In the getNumber method in object1 below, “this” is implicitly set to object1 because getNumber is a method of object1. However, we use the bind method to change the reference of “this” from object1 to object2.
const object1 = { myNumber: 1, getNumber: function () { return this.myNumber; },};const object2 = { myNumber: 2,};console.log(object1.getNumber()); // 1const boundNumber = object1.getNumber.bind(object2);console.log(boundNumber()); // 2
Arrow Functions: In contrast to other functions and methods, arrow functions inherit the value of “this” from their parent. In the example below, the parent is the window object so “this” refers to the window/global object.
const myArrowFunction = () => { console.log(this);};myArrowFunction(); // window object
Gotcha: Let’s say we have an object called myObject with a method called getNumber as shown below. If we create a new variable, myVariable, in the global scope and set it to myObject.getNumber, what should we expect the value of “this” to be if we called myVariable()? Bazinga, “this” refers to the global object. Why? When a invocation occurs without a reference to an object, then JavaScript assumes a function invocation in which case the execution context is the global object. Thus, “this” refers to the global object. As shown in the example below, the value of this.myNumber is undefined because “this” refers to the global object.
const myObject = { myNumber: 1, getNumber: function () { return this.myNumber; },};const myVariable = myObject.getNumber;console.log(myVariable()); // undefined