JavaScript ‘this’ keyword’s binding rules

JavaScript ‘this’ keyword’s binding rules

The ‘this’ keyword in JavaScript can be perplexing for many developers, and I, too, struggled with it for quite some time. If you’re one of those developers eager to grasp how ‘this’ works, you’re in the right place. By the end of this article, you’ll have a clear understanding of ‘this’. So, let’s get started!

Introduction

What is the ‘this’ keyword in JavaScript?

In JavaScript, the ‘this’ keyword refers to the object being executed in the current context, in other words, The ‘this’ keyword refers to different objects depending on how it is used.

What does the ‘this’ keyword allow you to do?

The ‘this’ keyword allows you to reuse functions with different contexts. In other words, it enables you to determine or specify which object should be the focus of the context when invoking a function or a method. For example, when you have multiple objects with the same properties and a single function, ‘this’ allows that function to work with all of your objects.

Binding rules

1. Global object binding

A. ‘this’ In the global context

If the ‘this’ keyword is not inside a function or an object, it refers to the window(global) object.

Example:

console.log(this);
console.log(this === window);
window.firstName = "Badreddine";
console.log(this.firstName);

As mentioned earlier, in the global context, ‘this’ refers to the window(global) object. In the example above, the first line logs the window object, and the second line logs true.

To illustrate further, we create a variable “firstName” on the window object and assign it the value “Badreddine”. Since ‘this’ refers to the window (global) object, the fourth line will log the value “Badreddine”, which is the value of the “firstName” property on the global (window) object.

Output:

console log of the code above

And it’s the same output whether it’s in strict mode or default mode.

B. ‘this’ in a regular function

‘this’ inside a regular function, when ‘this’ is used without any specific context, it still refers to the window object.

Example1:

function regularFunction() {
    console.log(this);
}

regularFunction();

Output:

console log of the code above

In strict mode, ‘this’ in a regular function is undefined.

Example2:

"use strict";
function regularFunction() {
    console.log(this);
}

regularFunction();

Output:

console log of the code above

Example3:

var firstName = "Badreddine";
function regularFunction() {
   const firstName = "Badr";
   console.log(firstName);
   console.log(this.firstName);
}

regularFunction();

In the example above we declare two variables with the same name, one inside the function and another one outside, what would be the output?

Output:

console log of the code above

The first log (fourth line) will output “Badr”. However, the second log (fifth line) will output “Badreddine”, and that’s because ‘this’ in regular function refers to the window(global) object as we said earlier.

Just one thing to add: if the outside “firstName” variable was declared with “let” or “const”, the output would be undefined, as shown below.

Example4:

const firstName = "Badreddine";
function regularFunction() {
   const firstName = "Badr";
   console.log(firstName);
   console.log(this.firstName);
}

regularFunction();

Output:

console log of the code above

And that’s because variables declared with “var” are added as properties of the global (window) object, unlike variables declared with “let” and “const”, which are not.

2. Implicit Binding

Implicit binding occurs when you invoke a function using dot notation, in those cases, we need to check the object adjacent to the method at invocation time. In simple words look at the function invocation, the object on its left is what ‘this’ refers to.

Example1:

const infos = {
   firstName: "Badreddine",
   job: "Frontend Developer",
   msg() {
      console.log(`Hi my name is ${this.firstName}, I'm a ${this.job}`);
   },
};

infos.msg();

In the example above we create an object “infos” with two properties and one method, then we invoke the “msg” method using infos.msg().

Output:

console log of the code above

The output is as expected, ‘this’ refers to the object on the left of the function invocation(“infos”).

Example2:

function infosMsg(obj) {
   obj.msg = function () {
      console.log(`Hi, my name is ${this.name}, I'm a ${this.job}`);
   };
}

const badreddine = {
   name: "Badreddine",
   job: "Frontend Developer",
};

const ali = {
   name: "Ali",
   job: "Backend Developer",
};

infosMsg(badreddine);
infosMsg(ali);

badreddine.msg();
ali.msg();

In the example above we create a function “infoMsg” that takes one parameter, “obj”. Inside the function, we add a new method called “msg” to the “obj” parameter.

Then, we create two objects, “badreddine” and “ali”, and called the “infoMsg” function for both objects.

Finally, we invoke the “msg” method on both objects.

Output:

console log of the code above

The output is as expected: when the method is called with the “badreddine object, ‘this’ refers to the “badreddine” object, and when it’s called with the “ali” object, it refers to the “ali” object.

3. Explicit Binding

Explicit binding occurs when we use call(), apply(), and bind() methods on a function. It’s called explicit because we explicitly specify the value of ‘this’.

The three methods are predefined JavaScript methods used to control the value of ‘this’, and they can accept additional arguments alongside the context(value of ‘this’) as the first argument.

A. call() method

The call() method can take ‘this’ as the first argument, followed by an unlimited number of arguments.

Example:

const infos = function (techOne, techTwo, techThre) {
   console.log(
      `Hi, my name is ${this.name}, I'm a ${this.job}, I use ${techOne}, ${techTwo} and ${techThre}`
   );
};

const me = {
   name: "Badreddine",
   job: "Frontend Developer",
};

const technologies = ["React", "TypeScript", "Tailwind"];

infos.call(me, technologies[0], technologies[1], technologies[2]);

In the example above we create a function “infos” which takes parameters, an object “me” and an array “technologies”.

We use the call() method on the “infos” function to invoke it. The call() method takes the “me” object as its first argument, which sets the value of ‘this’ inside the “infos” function to the “me” object. The subsequent arguments (technologies[0], technologies[1], and technologies[2]) are passed as arguments to the “infos” function.

Inside the “infos” function, “this.name” and “this.job” now refer to the properties of the “me” object

Output:

console log of the code above

B. apply() method

The apply() method can take a ‘this’ argument followed by an array of unlimited elements.

The difference between call() and apply() methods is how arguments are passed to the function. call() accepts arguments as individual parameters, while apply() accepts arguments as an array.

Example:

const infos = function (techOne, techTwo, techThre) {
   console.log(
      `Hi, my name is ${this.name}, I'm a ${this.job}, I use ${techOne}, ${techTwo} and ${techThre}`
   );
};

const me = {
   name: "Badreddine",
   job: "Frontend Developer",
};

const technologies = ["React", "TypeScript", "Tailwind"];

infos.apply(me, technologies);

In the example above we create a function “infos” which takes parameters, an object “me” and an array “technologies”.

We use the apply() method on the “infos” function to invoke it. The apply() method takes “me” object as its first argument, which sets the value of ‘this’ inside the “infos” function to the “me” object. The second argument (technologies) is an array that will be spread as arguments to the “infos” function.

Inside the “infos” function, “this.name” and “this.job” now refer to the properties of the ‘me’ object.

Output:

console log of the code above

C. bind() method

The bind() method can take ‘this’ as the first argument, followed by an unlimited number of arguments.

The difference between call() and bind(), is that call() immediately invokes the function, while bind() returns a new function which you can call it later.

Example:

const infos = function (techOne, techTwo, techThre) {
   console.log(
      `Hi, my name is ${this.name}, I'm a ${this.job}, I use ${techOne}, ${techTwo} and ${techThre}`
   );
};

const me = {
   name: "Badreddine",
   job: "Frontend Developer",
};

const technologies = ["React", "TypeScript", "Tailwind"];

const newFunc = infos.bind(me, technologies[0], technologies[1], technologies[2]);
newFunc();

In the example above we create a function “infos” which takes parameters, an object “me” and an array “technologies”.

Then we create a new function “newFunc” and initialize it with the returned function(the bind() method on the “infos” function). The bind() method takes “me” object as its first argument, which sets the value of ‘this’ inside the “infos” function to the “me” object. The subsequent arguments (technologies[0], technologies[1], and technologies[2]) are passed as arguments to the “infos” function.

Finally, we invoke the “newFunc” fonction

Inside the “infos” function, “this.name” and “this.job” now refer to the properties of the “me” object

Output:

console log of the code above

4. The new keyword

When you call a function with the new keyword in JavaScript, a new instance of the constructor is created, and ‘this’ keyword within the constructor function refers to the instance being created. Additionally, any properties and/or methods defined within the constructor will be assigned to that newly created instance.

A. function constructor

A function constructor is a regular JavaScript function that is used to create objects.

Example:

function Developer(name, job) {
   this.name = name;
   this.job = job;
   this.msg = function () {
      console.log(`Hi my name is ${this.name}, I'm a ${this.job}`);
   };
}

let me = new Developer("Badreddine", "Frontend Developer");
me.msg();

In the example above we define a constructor function called “Developer” that takes two parameters “name” and “job”.

Within the constructor, we use the ‘this’ keyword to assign values to the properties of the object being created (“this.name” and “this.job”). This allows us to initialize each instance of Developer with specific values for name and job.

Additionally, we define a method named “msg” within the constructor.

Then we create a new instance of the Developer object by calling new Developer(“Badreddine”, “Frontend Developer”). This instance is stored in the variable “me”.

Finally, we invoke the “msg” method on the “me” object.

Output:

console log of the code above

B. class

a class in JavaScript is essentially a syntactic way of defining constructor functions introduced in ES6.

Example:

class Developer {
   constructor(name, job) {
      this.name = name;
      this.job = job;
      this.msg = function () {
         console.log(`Hi my name is ${this.name}, I'm a ${this.job}`);
      };
   }
}

let me = new Developer("Badreddine", "Frontend Developer");
me.msg();

In the example above we define a class called “Developer” using the “class” keyword. This class serves as a blueprint for creating developer objects. Inside the Developer class, we define a constructor method using the “constructor” keyword. The constructor method takes two parameters, “name” and “job”, representing the name and job title of a developer, and it is automatically called when you create a new instance of the class.

Within the constructor method, we use the ‘this’ keyword to assign the provided name and job values to the object being created.

Additionally, we define a method called “msg” within the constructor method.

we create a new instance of the Developer class by calling new Developer(“Badreddine”, “Frontend Developer”). This instance is stored in the variable me.

Finally, you invoke the “msg” method on the “me” object.

Output:

console log of the code above

5. Arrow Functions

The arrow function is another way to create functions introduced in ES6. When you use ‘this’ with an arrow function, it would refer to the surrounding lexical context and that’s because it does not have its own context. The lexical context could be a parent function, a global context, or any enclosing scope where the arrow function is defined.

Example1:

const infos = {
   firstName: "Badreddine",
   msg: () => {
      console.log(`Hi, my name is ${this.firstName}`);
      console.log(this);
   },
};

infos.msg();

In the example above we create an object “infos” with a “firstName” property set to the string “Badreddine”.

Inside the “infos” object, we define a method “msg” using an arrow function syntax.

We call the “msg” method on the “infos” object.

Output:

console log of the code above

Now, here’s what happens when we run this code:

The first log attempts to access “this.firstName”. However, since arrow functions inherit the ‘this’ value from their surrounding lexical scope, and there is no outer function or scope that defines ‘this’, ‘this’ refers to the global object (window). Therefore, “this.firstName” will likely be undefined.

The second log logs the ‘this’ object, which will typically be the global object (window).

Example2:

const infos = {
    firstName: "Badreddine",
    msg() {
       let logMsg = () => {
          console.log(`Hi, my name is ${this.firstName}`);
          console.log(this);
       };
       logMsg();
    },
 };

 infos.msg();

In the example above we create an object named “infos” with a “firstName” property set to the string “Badreddine”.

Inside the “infos” object, we define a method “msg”.

Inside the “msg” method, we define a nested function called “logMsg” using the arrow function syntax and we call it.

Finally, we call the “msg” method on the “infos” object.

Output:

console log of the code above

Now, here’s what happens when we run this code:

The “logMsg” arrow function is called within the “msg” method.

Inside the “logMsg” function:

The first log attempts to access “this.firstName”. Since arrow functions inherit the ‘this’ value from their surrounding lexical scope (in this case, the “msg” method), “this.firstName” refers to the “firstName” property of the “infos” object.

The second log logs the ‘this’ object. Again, ‘this’ inherits the value from the surrounding lexical scope, which is the “msg” method. Therefore, it refers to the “infos” object.

Conclusion

. Recap of key points about ‘this’ in JavaScript.

. Alone, in a global context ‘this’ refers to the global(window) object, whether it’s in strict mode or default mode.

. In a regular function when ‘this’ is used without any specific context, in default mode, ‘this’ refers to the global(window)object, but in strict mode it’s undefined.

. In the implicit binding cases, ‘this’ refers to the object on the left side of the function at invocation time.

. In the explicit binding cases, ‘this’ refers to the value you explicitly specify.

. In an arrow function, ‘this’ does not have its own context, so it would refer to the surrounding lexical context.