1. Call & Apply

Using the call and apply methods we can invoke the function with the new context. The values will be attached to that execution only.

The difference between call and apply is that apply accepts arguments in an array, while call accepts it normally.

function showName(title) {
  console.log(title + " " + this.name);
}

function Example(name) {
  this.name = name;
}

const Example1 = new Example("John");
const Example2 = new Example("Anna");

showName.call(Example1, "Mr.");
showName.apply(Example2, ["Mrs."]);

// Output
Mr. John;
Mrs. Anna;

2. Bind - Permanent Binding

Permanent binding When using bind, we can create a new function with the new values and store it in a variable, and then use it further. It creates fresh permanent binding without affecting the original function.

function showName(title) {
  console.log(title + " " + this.name);
}

function Example(name) {
  this.name = name;
}

const Example1 = new Example("John");
const Example2 = new Example("Anna");

const showExample1 = showName.bind(Example1);
const showExample2 = showName.bind(Example2);

showExample1("Mr.");
showExample2("Mrs.");

// Output
Mr.John;
Mrs.Anna;

3. What is the output?

const person = { name: "Arjunan" };

function sayAge(age) {
  console.log(this.name + " is " + age + " years old.");
}

sayAge.call(person, 10); // Arjunan is 10 years old.
sayAge.bind(person, 10); //
sayAge.bind(person, 10)(); // Arjunan is 10 years old.

4. What is the output?

const person1 = {
  name: "Arjunan",
  age: 12,
  getAge: function () {
    console.log(this.age);
  },
};

const person2 = { age: 50 };

person1.getAge.call(person2); // 50
person1.getAge.apply(person2) // 50
person1.getAge.bind(person2)() // 50

5. What is the output?

var status = "Happy";

setTimeout(() => {
  const status = "Fruit";
  const data = {
    status: "Sad",
    getStatus() {
      console.log(this.status);
    },
  };
  data.getStatus(); // Sad
  data.getStatus.call(this); // Happy
}, 0);

// data.getStatus() have context of data when it is initialized and called.
// this in call have access to window and var Happy is defined in window.
// this will look for parent of the setTimeout callback which is window.

6. What is the output?

const animals = [
  { species: "Lion", name: "King" },
  { species: "Tiger", name: "Queen" },
];

function printAnimals(i) {
  this.print = function () {
    console.log(i + " " + this.species + ": " + this.name);
  };
  this.print();
}

for (let i = 0; i < animals.length; i++) {
  printAnimals.call(animals[i], i);
}

// 0 Lion: King
// 1 Tiger: Queen

7. Append Array to an Array

let a = [1, 2, 3]
let b = ["a", "b", "c"]

// Create new array
console.log([...a, ...b]) // [ 1, 2, 3, 'a', 'b', 'c' ]
console.log(a.concat(b)) // [ 1, 2, 3, 'a', 'b', 'c' ]

a.push(...b)
a.push.call(a, ...b)
a.push.apply(a, b)
console.log(a) // [ 1, 2, 3, 'a', 'b', 'c' ]

8. Bound Function

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

let user = {
  g: f.bind(null),
};

user.g(); // window
// the context of f() is set to window since bind is evoked with null

9. Bind Chaining

Once bind is set to a object, it will not change. There is nothing like bind chaining.

function f() {
  console.log(this.name);
}

let fn = f.bind({ name: "Arjun" }).bind({ name: "Ann" });
fn(); // Arjun

10. Fix Password Issue

success and failed function donot have access to this.name so inorder to fix this we need to provide functions by binding the user object to get access to this.name.

function checkPassword(success, failed) {
  let password = prompt("Password: ");
  if (password === "arjun") success();
  else failed();
}

let user = {
  name: "Appu",
  loginSuccess() {
    console.log(this.name + " logged in");
  },
  loginFailed() {
    console.log(this.name + " login failed");
  },
};

checkPassword(user.loginSuccess, user.loginFailed);
// arjun - undefined logged in
// xyz - undefined login failed

checkPassword(user.loginSuccess.bind(user), user.loginFailed.bind(user));
// arjun - Appu logged in
// xyz - Appu login failed

11. Partial Application of Login

function checkPassword(success, failed) {
  let password = prompt("Password: ");
  if (password === "arjun") success();
  else failed();
}

let user = {
  name: "Appu",
  login(result) {
    console.log(this.name + (result ? " logged in" : " login failed"));
  },
};

checkPassword(user.login.bind(user, true), user.login.bind(user, false));
// arjun - Appu logged in
// xyz - Appu login failed

12. Bind in Arrow Function

call, apply, bind donot make any difference in arrow function. It will work in the way arrow function is defined.

const age = 10;
let person1 = {
  name: "Arjun",
  age: 20,
  getAgeArrow: () => console.log(this.age),
  getAge: function () {
    console.log(this.age);
  },
};

let person2 = {
  age: 100,
};

person1.getAge(); // 20
person1.getAgeArrow(); // undefined

person1.getAge.bind(person2)(); // 100
person1.getAgeArrow.bind(person2)(); // undefined