Understanding "this" keyword  in JavaScript: "call", "apply", and "bind"

Understanding "this" keyword in JavaScript: "call", "apply", and "bind"

In JavaScript, the this keyword plays a crucial role in function context, and it can be dynamically scoped, leading to potential challenges. In this blog post, we'll explore the concepts of this, and how the methods call, apply, and bind can be used to explicitly set the this context for a function.

The this Keyword in JavaScript

The value of this in JavaScript is determined by how a function is called. A common rule is that this takes the value of the object to the left of the dot when invoking a method. Let's look at a simple example:

let x = {
  a() {
    return this;
  }
};
x.a(); // 'this' is the object 'x' in the body of 'a()'

In this example, when x.a() is called, this inside the method a refers to the object x. This is because this takes the value of the object to the left of the dot when invoking a method.

However, if the method is reassigned before calling it, the result changes:

let a = x.a;
a(); // Now, 'this' is undefined in the body of 'a()'

Here, you've assigned the function x.a to the variable a. When you invoke a(), the context of this changes. In this case, this is no longer bound to the object x; instead, it becomes undefined. This is because a is now a standalone function, and the context (this) is lost.

A clearer example with Date Formatting Function:

function fancyDate() {
  return `${this.getDate()}/${this.getMonth()}/${this.getFullYear()}`;
}

The fancyDate function is intended to be used as a method of a Date object, in this scenario this would refer to that Date object.

However, if you assign fancyDate to a variable and invoke it, the this context will be lost, resulting in errors or unexpected behavior.

function fancyDate() {
  return `${this.getDate()}/${this.getMonth()}/${this.getFullYear()}`;
}

// Assigning fancyDate to a variable
const myFunction = fancyDate;

// Invoking the function through the variable
const result = myFunction(new Date());

console.log(result);

The this context inside the fancyDate function (invoked through myFunction) will depend on whether the code is running in strict mode or non-strict mode.

In non-strict mode:

  • If the code is running in a browser environment, this will refer to the global object (window).

  • If the code is running in a Node.js environment or another JavaScript runtime, this will refer to the global object or be undefined, depending on the environment.

In strict mode:

  • Regardless of the environment, this will be undefined inside the fancyDate function.

In summary, the this keyword in JavaScript is dynamically scoped, and its value is determined by how a function is called. When you reassign a method or use a function independently, the this context can change, leading to potential issues. It's crucial to be aware of the context in which functions are invoked to ensure that this behaves as expected. Techniques like using bind, apply, or call can be employed to explicitly set the this context when needed.

The Trio: 'call', 'apply' and 'bind'.

These are methods provided by JavaScript to help programmers explicitly set the scope of the this keyword, let us look at each one of them in-depth.

Call:

// Usage:
function.call(thisArg, arg1, arg2, ...)

Purpose: Invokes the function immediately with the specified this context and individual arguments.

When to Use:

  • When you know the exact number of arguments that the function expects.

  • When you want to invoke the function immediately.

So back to our fancyDate() function:

function fancyDate() {
  return `${this.getDate()}/${this.getMonth()}/${this.getFullYear()}`;
}

// Assigning fancyDate to a variable
const myFunction = fancyDate;

// Invoking the function through the variable
const result = myFunction.call(new Date());

console.log(result);

In this example:

  1. The fancyDate function is defined to use the this context as if it were a method of a Date object.

  2. fancyDate is assigned to a new variable called myFunction.

  3. The call method is then used to invoke myFunction with a Date object as the this context.

    Apply:

//Usage
function.apply(thisArg, [arg1, arg2, ...])
  1. Purpose: Similar to call, but the arguments are passed as an array or an array-like object.

  2. When to Use:

    • When the number of arguments is dynamic or not known beforehand.

    • When you have an array-like object and want to use its elements as arguments.

function fancyDate() {
  return `${this.getDate()}/${this.getMonth()}/${this.getFullYear()}`;
}

// Assigning fancyDate to a variable
const myFunction = fancyDate;

// Invoking the function through the variable using apply
const result = myFunction.apply(new Date());

console.log(result);

In this example, the apply method is used to invoke myFunction with the this context set to a Date object (new Date()). The apply method takes an array or an array-like object as its second argument, but since fancyDate doesn't expect any additional arguments, you can call it with apply(new Date()).

Now, let's discuss what this will evaluate to inside the fancyDate function:

  • In this case, because apply is used and the this context is explicitly set to a Date object (new Date()), this inside the fancyDate function will refer to that Date object.

  • The getDate(), getMonth(), and getFullYear() methods will be called on the provided Date object, and the result will be a formatted date string.

So, in summary, this inside the fancyDate function will evaluate to the Date object specified in the apply method.

Bind

//Usage
 let boundFunction = function.bind(thisArg, arg1, arg2, ...)
  • Purpose: Creates a new function with the specified this context and, optionally, preset arguments.

  • When to Use:

    • When you want to create a reusable function with a fixed this context and, optionally, fixed arguments.

    • When you want to pass the function around or use it later without immediately invoking it.

function fancyDate() {
  return `${this.getDate()}/${this.getMonth()}/${this.getFullYear()}`;
}

// Assigning fancyDate to a variable
const myFunction = fancyDate;

// Creating a new function with bind to set the this context
const boundFunction = myFunction.bind(new Date());

// Invoking the bound function
const result = boundFunction();

console.log(result);

In this example:

  1. The bind method is used to create a new function (boundFunction) based on the original fancyDate function.

  2. The this context for boundFunction is explicitly set to a Date object (new Date()).

  3. The boundFunction is invoked, and this inside the fancyDate function will now refer to the Date object specified during the binding.

So, in summary, this inside the fancyDate function will evaluate to the Date object specified in the bind method. This allows you to create a new function with a fixed this context, making it useful for scenarios where you want to reuse the function with the same context multiple times.

Differences and Considerations:

  • call and apply are used for immediate invocation, while bind creates a new function that can be invoked later.

  • bind returns a new function with a fixed this context and, optionally, fixed arguments, whereas call and apply immediately invoke the function.

  • Use call or apply when you know the exact arguments at the time of invocation.

  • Use bind when you want to create a function with a fixed context or pre-set arguments that can be invoked later.

In practice, the choice between call, apply, and bind often depends on the specific requirements of your code and how you prefer to structure your functions. All three methods are valid, and they can be used interchangeably in many situations, but understanding their differences can help you make informed decisions based on your use case.