Skip to main content

Command Palette

Search for a command to run...

Demystifying this, call(), apply(), and bind() in JavaScript

Published
Demystifying this, call(), apply(), and bind() in JavaScript

'this'
The first thing that defines JavaScript is how it uses this.

this doesn't exist in other languages at the same level or in the same way it exists in JS.
JavaScript makes itself unique (and tricky) by this.

When we check the position of this in the browser

console.log(this)

We will receive the window object.

Run in the Node environment

console.log("this ===>", this)

We will receive

this ===> {}

Its use depends on the context.

When we run the below function in Node

node script.js

function ranveerOnGlobalStage() {
  return typeof this;
}
console.log("ranveerOnGlobalStage ==>", ranveerOnGlobalStage());

we will get

ranveerOnGlobalStage ==> undefined

because Node.js wraps every file inside a function internally and global is Node.js-specific.

If a function is not running in the global environment or it is in functional scope, at that time you will get typeof undefined instead of Object.

It means the function is not running in the real global scope or Node has enabled "use strict" mode by default.

Now interestingly you can make it global stage by using the call method.

console.log(ranveerOnGlobalStage.call(global));

Now you will return the global object of Node.

function ranveerOnGlobalStage() {
  return typeof this;
}
console.log("ranveerOnGlobalStage ==>", ranveerOnGlobalStage.call(global));

we will get

ranveerOnGlobalStage ==> object

How global and globalThis work you can check my link below

https://node-js.hashnode.dev/understanding-global-vs-globalthis-in-javascript-and-node-js

function ranveerWithNoScript() {
  return this;
}
console.log(ranveerWithNoScript()); // => return undefined
console.log(ranveerWithNoScript.call(global));

return <ref *1> Object [global] { global: [Circular *1], .....

What will output in the below function?

Guess where this is pointing.

If we run the code below

const bollywoodFilm2 = {
  name: "Dhurandhar",
  lead: "Ranveer",
  
  introduce() {
    return `\({this.lead} performs in \){this.name}`;
  },
};
console.log(bollywoodFilm2.introduce());

we will get

Ranveer performs in Dhurandhar

because here this context is available.

What can we expect this in the below function?

This is an arrow function.

const filmDirector = {
  name: "Sanjay Leela Bhansali",
  cast: ["Ranveer", "Deepika", "Priyanka"],

  announceCast() {
   this.cast
    .forEach((actor) => {
      console.log(`\({this.name} introduces \){actor}`);
    });
  },
};

filmDirector.announceCast()

What we observe is that the arrow function has printed this.

Normally arrow functions don't support this in the global environment, but here:

Arrow functions don't have their own this binding.

They inherit this from the surrounding (lexical) scope.

Sanjay Leela Bhansali introduces Ranveer
Sanjay Leela Bhansali introduces Deepika
Sanjay Leela Bhansali introduces Priyanka

If you want to know about arrow functions, you can check out my blog link on arrow functions

https://js-functions.hashnode.dev/

How can we expect the output in the below function with a nested arrow function?

const filmSet = {
  crew: "Spot boys",
  prepareProps() {
    console.log(`Outer this.crew: ${this.crew}`);

    const arrangeLights = () => {
      console.log(`Arrow this.crew: ${this.crew}`);
    };
    arrangeLights();
  },
};

filmSet.prepareProps();
Outer this.crew: Spot boys
Arrow this.crew: Spot boys

because this context is available, we receive the result.

They inherit this from the surrounding (lexical) scope.

What can we expect the result from the regular nested function?

const filmSet4 = {
  crew: "Spot boys",
  prepareProps() {
    console.log(`Outer this.crew: ${this.crew}`);

    function arrangeChairs() {
      console.log(`Inner this.crew: ${this.crew}`);
    }
    arrangeChairs();
  },
};

filmSet4.prepareProps();

Here we are calling the nested function without this context and defining it as a standalone method.

It's JavaScript behavior that here we lose the this context and get undefined.

arrangeChairs();

How can we fix it?

const filmSet5 = {
  crew: "Spot boys",
  prepareProps() {
    console.log(`Outer this.crew: ${this.crew}`);

    function arrangeChairs() {
      console.log(`Inner this.crew: ${this.crew}`);
    }
    arrangeChairs.bind(this);
  },
};

filmSet5.prepareProps();

The same function behaves differently based on how it's called:

function greet() {
  return console.log(`Hello, I'm ${this.name}`);
}

const person = { name: "Advait", greet };

console.log(person.greet()); 
console.log(greet.call(person1)); 
console.log(greet.apply(person1));

This is why JavaScript is said to have dynamic this binding — the value of this is determined at call-time, not at definition-time.

Now check the regular function without nested

function greet() {
  return console.log(`Hello, I'm ${this.name}`);
}

const person1 = { name: "Advait", greet };

// Hello, I'm Advait
console.log(person1.greet());
console.log(greet.call(person1));

And check the arrow function without nested in the global state

const greet = () => {
  console.log(`Hello, I'm Arrow function ${this.name}`);
};

const person2 = { name: "Alice", greet };
person2.greet();

// Hello, I'm Arrow function undefined

Not receiving the this context, it inherits this from the surrounding (lexical) scope.

Another classic JavaScript this binding pitfall

const actor = {
  name: "Ranveer",
  bow() {
    return `${this.name} takes a bow`;
  },
};

const detachedBow = actor.bow;
console.log(detachedBow());

Functions in JS are first-class citizens.

actor.bow is a reference to the function, not copying the object.

When called as actor.bow(), the object before the dot sets this.

When assigned to a variable and called directly, there's no object before the dot, so this defaults to global/undefined.

Solution

const boundBow = actor.bow.bind(actor);

Conclusion behaviour of JS

Regular function has its own this in the global state.

It depends purely on HOW the function is called.

But they don't have this in nested functions until you set it using the .call method.

It doesn't have variable access there. In detached methods also it doesn't have variable access there.

Arrow functions don't have their own this in the global state.

They don't have variable access there, but in nested functions they capture the outer scope this.

They take the reference of the variable.

Regular function: this is dynamic (call-site based), creates new memory

Arrow function: this is lexical (scope based)


Behind the scene

When JS runs code, it creates:

  1. Lexical Environment (Scope / Variables Memory)

  2. Execution Context (Includes this)

this is NOT part of the normal variable scope.

It lives in the Execution Context, not inside the lexical variable environment.

The Core Purpose of call, apply, and bind.

call, apply, and bind allow you to explicitly define what this should refer to, regardless of the calling context.

1. call()

The call() method invokes a function immediately.

function cookDish(ingredient, style){
    return `\({this.name} prepares \){ingredient} in ${style} style !`
}

const guptaKitchen = {name : "Gupta jis kitchen"}

console.log(cookDish.call(guptaKitchen,"Panner bhrju", "Novaeg"));

2. apply()

The apply() method is almost identical to call(), but instead of passing arguments individually, you pass them as a single array.

function cookDish(ingredient, style){
    return `\({this.name} prepares \){ingredient} in ${style} style !`
}

const guptaOrder = ["Chole kulche", "Punjabi Dhaba"]

console.log(cookDish.apply(guptaKitchen, guptaOrder));

const bills = [100, 30, 45, 50];
console.log(Math.max.apply(null, bills))

3. bind()

Unlike the other two, bind() does not execute the function immediately. Instead, it returns a new copy of the function with the this value permanently set to the provided object.

function reportDelivery(location, status){
    return `\({this.name} at \){location}: ${status}`;
}
const deliveryBoy = { name: "Ranveer" };

console.log("Call", reportDelivery.call(deliveryBoy, "Mumbai", "Order"))
console.log("apply", reportDelivery.call(deliveryBoy, ["Moon", "Pending"]))
console.log("Bind", reportDelivery.bind(deliveryBoy, ("Haridwar", "What")))

const bindReport = reportDelivery.bind(deliveryBoy)
console.log(bindReport("Haridwar", "What"));

More than The Core Purpose of this binding

1.Function Borrowing

const calculator = {
  value: 0,
  add(num) { this.value += num; return this; },
  subtract(num) { this.value -= num; return this; }
};

const otherCalc = { value: 10 };

// Borrow methods from calculator
calculator.add.call(otherCalc, 5);
calculator.subtract.apply(otherCalc, [3]);
console.log(otherCalc.value); // 12

// Borrow array methods for array-like objects
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
const realArray = Array.prototype.slice.call(arrayLike);
console.log(realArray); // ['a', 'b']

**2.**Partial Application (Currying)

function multiply(x, y, z) {
  return x * y * z;
}

// Create specialized functions
const multiplyByTwo = multiply.bind(null, 2);
const multiplyByTwoAndThree = multiply.bind(null, 2, 3);

console.log(multiplyByTwo(4, 5));     // 2 * 4 * 5 = 40
console.log(multiplyByTwoAndThree(5)); // 2 * 3 * 5 = 30

3. Function Composition

function compose(...functions) {
  return functions.reduce((f, g) => 
    (...args) => f.call(null, g.apply(null, args))
  );
}

const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;

const composed = compose(addOne, double, square);
console.log(composed(3)); // square(3) = 9, double(9) = 18, addOne(18) = 19