The call
, apply
and bind
functions are extremely helpful functions in JavaScript that allow us to bind and invoke functions with a different execution context, attach their value of this
to an object of our preference.
Note: This blog post requires some basic knowledge of JavaScript and execution contexts.
Resources:
We’re just going to use Prototypes to add a common callWith
function to all functions in our code, it will act as a polyfill for the call
function.
So a little context:
const obj = {
a: 1,
b() {
console.log(this.a);
}, // 1
};
The value of this
inside a function declared or attached to an object is the object itself. We’ll be using this to our advantage in our implementation of call
, apply
and bind
.
Let’s check the syntax of the call function and try creating our own:
Function.prototype.callWith = function (context, ...args) {
const updatedContext = {
...context,
func: this, // 'this' refers to the function that this prototype is running on
};
// We have to invoke the function now. The 'this' keyword will now refer to 'updatedContext' that the user has passed.
return updatedContext.func(...args);
/*// Simpler approach, but readable code is better than performant code
context.func = this;
context.func(...args);
*/
};
// Usage
function nonBindedFunc(arg1, arg2) {
console.log(this.a, arg1, arg2);
}
nonBindedFunc.callWith({ a: 1 }, 2, 3);
The apply
function is very similar to call
, the only difference being that the arguments to the function are passed as an array instead of regular function arguments. So there’s only one change we have to make to the function, which is the type of args
.
Function.prototype.applyWith = function (context, args = []) {
const updatedContext = { ...context, func: this };
return updatedContext.func(...args);
};
// Usage
function nonBindedFunc(arg1, arg2) {
console.log(this.a, arg1, arg2);
}
nonBindedFunc.applyWith({ a: 1 }, [2, 3]);
Unlike call
and apply
that create a new binded function and also invoke it, bind
just returns a copy of the binded function and let’s you choose when to invoke it.
Since we already have created our applyWith
and callWith
prototype functions, we can simply use them to our advantage to create our implementation of bindWith
.
Function.prototype.bindWith = function (context, ...args) {
const func = this;
return function () {
return func.applyWith(context, args); // or func.callWith(context, ...args);
};
};
// Usage
function nonBindedFunc(arg1, arg2) {
console.log(this.a, arg1, arg2);
}
const bindedFunc = nonBindedFunc.bindWith({ a: 1 });
// ... You decide when to execute this now since the function is already binded
bindedFunc(2, 3);
Note: Instead of using ...args
everywhere, in case we don’t know about the list of arguments, we could use the inbuild arguments
variable available to each function for dynamic arguments passed into a function.
Exceptions: Arrow functions do not work with these approaches, since their working of this
keyword is different. For reference, check this page.