Borrowing Methods in JavaScript

April 17, 2018 0 Comments

Borrowing Methods in JavaScript

 

 

In JavaScript, sometimes it’s desirable to reuse a function or method on a different object other than the object or prototype it was defined on. By using call(), apply() and bind(), we can easily borrow methods from different objects without having to inherit from them – a useful tool in a professional JavaScript developer’s toolbox.

This article assumes you already have a working knowledge of call(), apply() and bind() and their differences.

Methods from native prototypes

In JavaScript, almost everything you touch is an object except for primitives such as string, number and booleans which are immutable. An Array is a type of object suited to traversing and mutating an ordered list of data, and comes with useful methods on its prototype such as slice, join, push and pop.

A common use case we see with objects is to “borrow” methods from an array as they are both list type data structures. The most common borrowed method is Array.prototype.slice.

function myFunc() { // error, arguments is an array like object, not a real array arguments.sort(); // "borrow" the Array method slice from its prototype, which takes an array like object (key:value) // and returns a real array var args = Array.prototype.slice.call(arguments); // args is now a real Array, so can use the sort() method from Array args.sort(); } myFunc('bananas', 'cherries', 'apples');

Borrowing methods is possible due to call and apply allowing us to invoke functions in a different context and is a great way to reuse existing functionality without having to make one object extend from another. An array actually has many methods defined on its prototype that are considered generically reusable, two further examples of which are join and filter:

// takes a string "abc" and produces "a|b|c Array.prototype.join.call('abc', '|'); // takes a string and removes all non vowels Array.prototype.filter.call('abcdefghijk', function(val) { return ['a', 'e', 'i', 'o', 'u'].indexOf(val) !== -1; }).join('');

As you can see it’s not just objects who benefit by borrowing methods from an array, strings can too. However, since generic methods are defined on the prototype having to write String.prototype or Array.prototype each time we want to borrow a method is verbose and fast becomes tiresome. An alternative, valid approach that is effectively the same is to use Literals.

Borrowing Methods using Literals

A Literal is is a syntactic language construct that follows the rules of JavaScript and is explained by MDN as:

You use literals to represent values in JavaScript. These are fixed values, not variables, that you literally provide in your script

Literals allow us to access prototype methods in short form:

[].slice.call(arguments); [].join.call('abc', '|'); ''.toUpperCase.call(['lowercase', 'words', 'in', 'a', 'sentence']).split(',');

This is less verbose but still looks a bit ugly having to operate on [] and "" directly to borrow their methods. We can shorten this even further by storing a reference to the literal and its method as a variable:

var slice = [].slice; slice.call(arguments); var join = [].join; join.call('abc', '|'); var toUpperCase = ''.toUpperCase; toUpperCase.call(['lowercase', 'words', 'in', 'a', 'sentence']).split(',');

With a reference to the borrowed method, we can simply invoke it using call() and enjoy all the benefits of reusability. Continuing in the spirit of reducing verboseness, let’s see if we can borrow a method and not have to write call() or apply() each time we want to invoke it:

var slice = Function.prototype.call.bind(Array.prototype.slice); slice(arguments); var join = Function.prototype.call.bind(Array.prototype.join); join('abc', '|'); var toUpperCase = Function.prototype.call.bind(String.prototype.toUpperCase); toUpperCase(['lowercase', 'words', 'in', 'a', 'sentence']).split(',');

As you can see, you can now statically bind “borrowed” methods from many different native prototypes using Function.prototype.call.bind, but how does var slice = Function.prototype.call.bind(Array.prototype.slice) actually work?

Understanding Function.prototype.call.bind

Function.prototype.call.bind looks a little complicated at first but it’s super useful to understand how it works.

  • Function.prototype.call is a reference to “call” a function and set its “this” value to be used inside said function.
  • Remember “bind” returns a new function that always remembers its “this” value. Therefore, .bind(Array.prototype.slice) returns a new function with its “this” permanently set to the Array.prototype.slice function.

By combining both the above, we now have new function that will invoke the “call” function with its “this” bounded to the “slice” function. Invoking slice() simply becomes a reference to the previous bounded method.

Methods from custom objects

Inheritance is great but often developers resort to it when they want to reuse some common functionality between objects or modules. If you’re using inheritance solely for code reuse you’re probably doing something wrong, and in most situations simply borrowing a method will get you a long way.

So far we’ve only talked about borrowing native methods, but it’s possible to borrow any method! Take the following code to calculate a players score game score:

var scoreCalculator = { getSum: function(results) { var score = 0; for (var i = 0, len = results.length; i < len; i++) { score = score + results[i]; } return score; }, getScore: function() { return scoreCalculator.getSum(this.results) / this.handicap; } }; var player1 = { results: [69, 50, 76], handicap: 8 }; var player2 = { results: [23, 4, 58], handicap: 5 }; var score = Function.prototype.call.bind(scoreCalculator.getScore); // Score: 24.375 console.log('Score: ' + score(player1)); // Score: 17 console.log('Score: ' + score(player2));

Although the above example is contrived, it's easy to see just like native methods how user defined methods can be easily borrowed too.

Wrapping up

Call, bind and apply allow us to change the way functions are invoked and are typically used when borrowing a function. The majority of developers are familiar with borrowing native methods but less so with user defined.

In the last few years functional programming in JavaScript has been on the rise and I expect short cutting how to borrow methods using Function.prototype.call.bind will become more common.


Tag cloud