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.

Prerequisite

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.

Comments

  1. Technically, the “literal” syntax for arrays, regexes, and plain objects is known as an “initializer” (even though the spec does use terms like “ObjectLiteral”); every time the expression is encountered, a new instance is created (the same is true for function expressions, which is why JSLint has long warned against “creating functions” in loops).

    It’s probably not too much overhead to use “[].slice” instead of “Array.prototype.slice”, creating and soon garbage-collecting a new empty array in the process, and for the case of strings, the difference is even smaller, because strings actually are literal values in JS.

    (Regexes were once a special case, in which two appearances of the same regex initializer actually would refer to the same RegExp object, with the same match index and the like.)

    Reply
  2. Pingback: Borrowing Methods in JavaScript by David Sharif...

  3. probably worth explaining that

    var score = Function.prototype.call.bind(scoreCalculator.getScore);
    score(player1);

    is doing this

    scoreCalculator.getScore.call(player1)

    and that you can borrowing the `call` method from anywhere, in this case from the Function.prototype, but it doesn’t matter where and binding it to the getScore function.

    It’s confusing to see the Function constructor there – especially since you are constructing a function – because it’s not involved.

    Could just a s easily write…

    var score = scoreCalculator.getScore.call.bind(scoreCalculator.getScore)

    or…

    var score = function score(){}.call.bind(scoreCalculator.getScore)

    Reply
  4. Dear David,
    sorry i couldn’t find your email. When does the e-book will publish? I am looking forward it.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *


4 × = thirty two

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>