Understanding call, apply and bind methods through polyfills

Understanding call, apply and bind methods through polyfills

ยท

6 min read

Introduction

Knowing polyfills of well-known and most widely used JavaScript methods, makes any developer understand its working more deeply and helps to grow in his/her career. I am going to discuss some of them which include - call(), apply() and bind() and try to implement them with their polyfills and also understand them. So, let's get started. Let's get started gif

TL;DR

Are you in a hurry โณ? No problem, here are the snippets for you.

// Polyfill for call()
Function.prototype.myCall = function (...args) {
  if (typeof this !== "function") {
    throw new Error(`${this}.myCall is not a function`);
  }
  const params = [...args];
  const context = params[0];
  const otherArgs = params.slice(1);
  context.fun = this;
  return context.fun(...otherArgs);
};

// Polyfill for apply()
Function.prototype.myApply = function (context, argsArr) {
  if (typeof this !== "function") {
    throw new Error(`${this}.myApply is not a function`);
  }
  if (!Array.isArray(argsArr)) {
    throw new TypeError("CreateListFromArrayLike called on non-object");
  }
  context.fun = this;
  return context.fun(...argsArr);
};

// Polyfill for bind()
Function.prototype.myBind = function (...args) {
  if (typeof this !== "function") {
    throw new Error(`${this}.myBind is not a function`);
  }
  const params = [...args];
  const context = params[0];
  const otherArgs = params.slice(1);
  context.fun = this;
  return function (...innerArgs) {
    return context.fun(...otherArgs, ...innerArgs);
  };

Understanding call()

const name = {
  firstName: "Mary",
  lastName: "Jane",
};

function printUser (address, state, country) {
  return `${this.firstName} ${this.lastName} from ${address}, ${state}, ${country}`;
}

const output = printUser.call(name, "Kol", "WB", "IN");
console.log(output);

//Expected Output: "Mary Jane from Kol, WB, IN"

The call() takes this value as a parameter and optional arguments provided individually. In the above block of code, name is passed as this value and "Kol", "WB", "IN" are passed as arguments. So, the statement printUser.call(name, "Kol", "WB", "IN") means that invoke the printUser function taking name object as context along with other parameters.

Understanding call() Polyfill

const name = {
  firstName: "Mary",
  lastName: "Jane",
};

function printUser (address, state, country) {
  return `${this.firstName} ${this.lastName} from ${address}, ${state}, ${country}`;
}

Function.prototype.myCall = function (...args) {
  if (typeof this !== "function") {
    throw new Error(`${this}.myCall is not a function`);
  }
  const params = [...args];
  const context = params[0];
  const otherArgs = params.slice(1);
  context.fun = this;
  return context.fun(...otherArgs);
};

const output = printUser.myCall(name, "Kol", "WB", "IN");
console.log(output);

//Expected Output: "Mary Jane from Kol, WB, IN"

Here we are using Function.prototype so that we can use bind our own version of call() i.e., myCall() to any function. After receiving all the arguments, we check whether our version of call() is bound to a method or not. If not, then throw an error. Then we spread the arguments in the params array and took out the context which is the name object here present at the 0th index and separated other arguments as an array. Then we assign a key fun to the name object which holds the function on which myCall() is bounded (as this holds the printUser function) and return its value by invoking it, passing other arguments as parameters.

To read more about Function.prototype or Object prototyping in JavaScript, read here.

Understanding apply()

const name = {
  firstName: "Mary",
  lastName: "Jane",
};

function printUser (address, state, country) {
  return `${this.firstName} ${this.lastName} from ${address}, ${state}, ${country}`;
}

const output = printUser.apply(name, ["Kol", "WB", "IN"]);
console.log(output);

//Expected Output: "Mary Jane from Kol, WB, IN"

The apply() takes this value as a parameter and optional arguments are provided as an array. The syntax is almost similar to call() except it takes optional arguments as an array.

Understanding apply() Polyfill

const name = {
  firstName: "Mary",
  lastName: "Jane",
};

function printUser (address, state, country) {
  return `${this.firstName} ${this.lastName} from ${address}, ${state}, ${country}`;
}

Function.prototype.myApply = function (context, argsArr) {
  if (typeof this !== "function") {
    throw new Error(`${this}.myApply is not a function`);
  }
  if (!Array.isArray(argsArr)) {
    throw new TypeError("CreateListFromArrayLike called on non-object");
  }
  context.fun = this;
  return context.fun(...argsArr);
};

const output = printUser.myApply(name, ["Kol", "WB", "IN"]);
console.log(output);

//Expected Output: "Mary Jane from Kol, WB, IN"

Here we are using Function.prototype so that we can use bind our own version of apply() i.e., myApply() to any function. It's good to check whether our version of apply() is bound on a method or not and also optional arguments are in the form of an array or not. If not, then throw an error in both cases. Then we assign a key fun to the name object which holds the function on which myApply() is bounded (as this holds the printUser function) and return its value by invoking it, passing other arguments as parameters.

To read more about Function.prototype or Object prototyping in JavaScript, read here.

Understanding bind()

const name = {
  firstName: "Mary",
  lastName: "Jane",
};

function printUser (address, state, country) {
  return `${this.firstName} ${this.lastName} from ${address}, ${state}, ${country}`;
}

const outputFun = printUser.bind(name, "Kol", "WB");
const output = outputFun("IN");
console.log(output);

//Expected Output: "Mary Jane from Kol, WB, IN"

The bind() takes this value as a parameter and optional arguments are provided individually. It returns a copy of the function on which it has been bound having this keyword set to the provided value.

Understanding bind() Polyfill

const name = {
  firstName: "Mary",
  lastName: "Jane",
};

function printUser (address, state, country) {
  return `${this.firstName} ${this.lastName} from ${address}, ${state}, ${country}`;
}

Function.prototype.myBind = function (...args) {
  if (typeof this !== "function") {
    throw new Error(`${this}.myBind is not a function`);
  }
  const params = [...args];
  const context = params[0];
  const otherArgs = params.slice(1);
  context.fun = this;
  return function (...innerArgs) {
    return context.fun(...otherArgs, ...innerArgs);
  };
};

const outputFun = printUser.myBind(name, "Kol", "WB");
const output = outputFun("IN");
console.log(output);

//Expected Output: "Mary Jane from Kol, WB, IN"

Here we are using Function.prototype so that we can use bind our own version of bind() i.e., myBind() to any function. After receiving all the arguments, we check whether our version of bind() is bound on a method or not. If not, then throw an error. Then we spread the arguments in the params array and took out the context which is the name object here present at the 0th index and separated other arguments as an array. Then we assign a key fun to the name object which holds the function on which myBind() is bounded (as this holds the printUser function) and return another function which in turn returns invoked this function with all the parameters.

To read more about Function.prototype or Object prototyping in JavaScript, read here.

Wrapping up

Polyfills are highly asked questions in interviews and really come in handy if you know it beforehand. Read more about call, apply and bind methods on MDN Docs.

Hope this article was helpful ๐Ÿ˜Š. If so, then do give a reaction and share with others. Want to add something? Write it down in the comments ๐Ÿ‘‡