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.
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 ๐