Creating Our Own JavaScript Call, Apply And Bind Functions

Photo By Pixabay from Pexels

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:

Function.call

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: image.png

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);

Function.apply

image.png

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]);

Function.bind

image.png

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.