Javascript Icon Get 62% off the JavaScript Master bundle

See the bundle then add to cart and your discount is applied.

0 days
00 hours
00 mins
00 secs

Write JavaScript like a pro. Javascript Icon

Follow the ultimate JavaScript roadmap.

Polyfills suck use a featurefill instead

I’m going to dub this a featurefill as the post title suggests, but it’s more a feature-detect-closure-binding-smart-polyfill-api-checker-reusable-function-awesomeness.

So, what’s the deal?… I’m a huge fan of polyfilling behaviour for older browsers that don’t support specific APIs, such as Function.prototype.bind or Array.prototype.forEach. Typically, we’d drop these polyfills into our apps like so:

Table of contents

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError(
        'Function.prototype.bind - what is trying to be bound is not callable'
      );
    }

    var aArgs = Array.prototype.slice.call(arguments, 1),
      fToBind = this,
      fNOP = function() {},
      fBound = function() {
        return fToBind.apply(
          this instanceof fNOP && oThis ? this : oThis,
          aArgs.concat(Array.prototype.slice.call(arguments))
        );
      };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}

Then we can get on with our development and start using Function.prototype.bind until our heart’s content.

The thing I’m questioning though, is this a good way to do things? Our if statement checks for the existence of a method on the prototype chain, and if it’s not there it patches it.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

I’m thinking there may be better ways to do this. Instead of checking for something that doesn’t exist, and hope there are no other polyfills that create strange behaviour from modifying our Objects, we could wrap the contents of our polyfill inside a clever closure, and return if the API does exist, rather than if it doesn’t.

This would require us to construct our own methods, however, but it tightly packs the functionality for solid code reuse in later projects.

A quick starting function to demonstrate the concept, we’ll create an isArray method, instead of polyfilling the ES5 method:

function isArray(collection) {}

Let’s check out the polyfill for the isArray method:

if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}

Again, it detects for the presence of the method and creates it for us if it doesn’t exist. Let’s detect if the method does exist, and get smarter with our polyfilling by using the native methods when available first.

function isArray(collection) {
  if (Array.isArray) {
    return Array.isArray(collection);
  }
}

We don’t need an else statement because we’ll have already returned if the method exists, so we’ll drop a modified version of the above !Array.isArray polyfill in:

function isArray(collection) {
  if (Array.isArray) {
    return Array.isArray(collection);
  }
  return Object.prototype.toString.call(collection) === '[object Array]';
}

Done. Simple! This uses the native method when available, and gracefully falls back to a polyfill if it’s not.

There is one slight problem with this approach, however, in that the if statement is checked every time the function is called. We’ll use a closure to return only the things we need at runtime, to increase the performance of multiple checks.

First, we’ll switch the isArray function for a variable:

var isArray;

Then assign an IIFE:

var isArray = (function() {})();

This function executes immediately, so we can return a value to it so it’s bound for the lifetime of the program. Should Array.isArray be natively available, let’s give that back to the developer:

var isArray = (function() {
  if (Array.isArray) {
    return Array.isArray;
  }
})();

Notice we don’t now have to call Array.isArray(collection);, we just return the native API. The polyfill requires returning a function closure with our collection argument, which then returns what we need for the polyfill:

var isArray = (function() {
  if (Array.isArray) {
    return Array.isArray;
  }
  return function(collection) {
    return Object.prototype.toString.call(collection) === '[object Array]';
  };
})();

If our browser supports the Array.isArray method then we’ll actually get this (if we logged it out in the console):

function isArray() { [native code] }

If our browser doesn’t support it, we get the patch:

function (collection) {
  return Object.prototype.toString.call(collection) === '[object Array]';
}

This is great, as it means that we’re actually getting a single return value bound to the variable, so there’s no if checking for API presence and we should get some performance gains from this.

If you’re running test suites on your functions, baking your own library, then using this method I’d highly recommend over dropping in random polyfills. It provides expected behaviour that doesn’t modify the existence of APIs that might or might not be there.

Learn JavaScript the right way.

The most complete guide to learning JavaScript ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover