In last week’s post, I wrote that you can use the super keyword to invoke a method on an object’s prototype. Though much older, the Function.prototype.call method is a useful—and more versatile—alternative.

Here’s the super example from last week. In the body of the warriorProto.greet method, we use the super keyword to invoke the heroProto.greet method:

let heroProto = {
greet() {
return `As a hero, ${this.name} says hello.`;
},
};

let warriorProto = {
__proto__: heroProto,
greet() {
let greeting = super.greet(); // Invoke method on prototype
greeting += `\nAs a warrior, ${this.name} says "take no prisoners!"`;
return greeting;
},
};

let warrior = {
__proto__: warriorProto,
name: "Bjorn",
level: 1,
weapon: "axe",
};

Modified to use the Function.prototype.call method, the body of the warriorProto.greet method looks like this:

let warriorProto = {
__proto__: heroProto,
greet() {
let greeting = heroProto.greet.call(this); // Invoke method on prototype
greeting += `\nAs a warrior, ${this.name} says "take no prisoners!"`;
return greeting;
},
};

Instead of invoking super.greet(), we invoke heroProto.greet.call(this). Because heroProto.greet is a Function object, it has access to the Function.prototype.call method. This lets us invoke heroProto.greet with a given this value.

As long as we have an object with a .name property, we can pass it to heroProto.greet.call() and it will work—even if the object doesn’t inherit from heroProto. This arguably makes the Function.prototype.call method more versatile than the super keyword:

let heroProto = {
greet() {
return `As a hero, ${this.name} says hello.`;
},
};

let dragonborn = {
name: "Dovahkiin",
};

let greeting = heroProto.greet.call(dragonborn);
console.log(greeting); // "As a hero, Dovahkiin says hello."

Before spread syntax and the Array.from() method, this technique was often used to convert an array-like object to a true array. It works because, like a true array, an array-like object has numbered keys and a .length property, which is what the Array.prototype.slice() method expects:

let divs = document.getElementsByTagName("div");
let isArray = Array.isArray(divs);
console.log(isArray); // false

divs = Array.prototype.slice.call(divs);
isArray = Array.isArray(divs);
console.log(isArray); // true

People sometimes use this technique to get the “true” type of an object as well. For example, Array, Function, and RegExp objects all shadow (override) the Object.prototype.toString() method. We can use the Function.prototype.call method to get around this:

let foo = [];
let type = Object.prototype.toString.call(foo);
console.log(type); // "[object Array]"

foo = function () {};
type = Object.prototype.toString.call(foo);
console.log(type); // "[object Function]"

foo = /(?:)/;
type = Object.prototype.toString.call(foo);
console.log(type); // "[object RegExp]"

These are just a few examples, but hopefully you can appreciate the versatility of this technique. However, if you just want to invoke a method on an object’s prototype, I would argue that the super.method() syntax makes your intention clearer.