What is a Polyfill in JavaScript?

What is a Polyfill in JavaScript?

A polyfill, in the context of JavaScript, is a piece of code (usually written in JavaScript) that provides functionality that is not natively supported by a web browser.

The term "polyfill" is a combination of "poly," meaning many, and "fill," indicating filling in the gaps. Polyfills are used to bring new or standardized features to older browsers or environments that do not inherently support those features.

When new JavaScript features are introduced or when web standards evolve, not all browsers immediately implement those changes. This can create a situation where developers want to use the latest language features or APIs but still need to support older browsers. In such cases, they can include polyfills to ensure that the required functionality is available across a broader range of browsers.

Polyfills usually emulate a newer API that provides fallback functionality to older browsers. Polyfills can also make a feature that different browsers implement differently work the same way in each browser.

There are varying degrees of completeness for a polyfill:

  • Perfect polyfills are polyfills that perfectly implement features completely without any side effects. An example is the JSON library, which implements JSON.stringify and JSON.parse on older browsers that do not already support the global JSON object.

  • Common polyfills are polyfills that implement features almost completely. They might have a few small missing features, some edge cases that don't work correctly, or there might be some slightly annoying side effects. Most polyfills fall into this category.

  • Partial polyfills implement certain features, but have a lot of missing or broken functionality. An example of this is the ES5 shim, which implements a lot of ECMAScript 5 features for ECMAScript 3 engines, but is missing several key features.

  • Fallback polyfills are polyfills that don't implement the new functionality at all, but just make sure that there is a graceful fallback behavior for older browsers. An example of this is a Web Worker fallback. In HTML5, using web workers, you can execute JavaScript code in multiple threads, but there is no known way to do this in older browsers. This fallback just makes all the multi-threaded code run in a single thread, which allows the code to run in older browsers, but the browser will freeze while the code is running.

Polyfills usually have two basic components: feature detection and feature implementation.

1.Feature Detection

First, you have to test whether the given feature is already implemented in the browser. If it is, you don't want to reimplement anything that already exists and you should just stop here. Otherwise, if the browser actually is missing the feature, you can proceed to the next step.

This step is omitted in overriding polyfills where you override any existing behavior and instead use your implementation. This approach guarantees that every browser will behave the way you expect, but it has the disadvantage of causing potentially unnecessary overhead from overriding the native implementation of a feature.

2.Feature Implementation

This is the meat of the polyfill where you actually implement the missing feature.

First Polyfill Example: Adding Prototype Methods

In ECMAScript 5, there were many new Array prototype methods added that emphasized a functional programming approach to manipulating arrays. An example of this is the filter method, which accepts a function and returns an array containing only the values of the original array for which the function returns true. For example, you could filter an array to only contain even values.

JavaScript:

let isEven = function(n) {
  return n % 2 === 0;
};

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter(isEven);
// returns [2, 4, 6, 8, 10]

The filter method has an optional second parameter to bind to the this value of the function. For example, you could bind an object as this to the function passed to filter.

JavaScript:

let fruits = {
  banana: "yellow",
  strawberry: "red",
  pumpkin: "orange",
  apple: "red"
};
let isRedFruit = function(name) {
  return this[name] === "red";
};
["pumpkin", "strawberry", "apple", "banana", "strawberry"].filter(isRedFruit, fruits);
// returns ["strawberry", "apple", "strawberry"]

Since this was a feature added in ES5, older browsers, such as Internet Explorer 8 and below, do not support the filter method. Fortunately, this feature is easy to create a polyfill for.

First, we have to do the feature detection to see if the filter method is already supported. In this case, we just need to check if there is a function in the Array prototype named filter. If not, we can create it.

JavaScript:

if(typeof Array.prototype.filter !== "function") {
  Array.prototype.filter = function() {
    // implementation goes here
  };
}

And now we can write the implementation. Notice the checks it makes for edge cases.

JavaScript:

Array.prototype.filter = function(fn, thisp) {
  if (this === null) throw new TypeError;
  if (typeof fn !== "function") throw new TypeError;
  let result = [];
  for (let i = 0; i < this.length; i++) {
    if (i in this) {
      let val = this[i];
      if (fn.call(thisp, val, i, this)) {
        result.push(val);
      }
    }
  }
  return result;
};

It turns out that this is just a common polyfill, rather than a perfect polyfill, because it causes unexpected behavior in for..in loops by adding "filter" as an enumerable property to every array.

JavaScript:

let arr = [0, 1, 2];
for(let i in arr) {
  console.log(i);
}

//LOG: 0 
//LOG: 1 
//LOG: 2 
//LOG: filter

This is more of a fun learning example than something that is really practical--showing how to do this might even be actively harmful. The blink tag is a non-standard element that causes its contents to blink on and off. Fortunately, the only modern browsers that support this tag are Mozilla Firefox and Opera.

Unfortunately, there is no known straightforward way to detect if the browser supports the blink tag (but if you figure one out, I'd be interested to hear about it). This means that we will have to write an overriding polyfill, which will override whatever built-in behavior a browser has for the blink tag.

Instead of the first step being feature detection, the first step here will be to replace all the blink tags with some other tag that no browsers will cause to blink, and will not conflict with any existing styles. Here, I will replace all the blink tags with blinky tags.

JavaScript:

(function replaceBlinks() {
  let blinks = document.getElementsByTagName("blink");
  while (blinks.length) {
    let blink = blinks[0];
    let blinky = document.createElement("blinky");
    blinky.innerHTML = blink.innerHTML;
    blink.parentNode.insertBefore(blinky, blink);
    blink.parentNode.removeChild(blink);
  }
})();

With that out of the way, we can implement the actual blinking behavior.

JavaScript:

(function blink(visible) {
    let blinkies = document.getElementsByTagName("blinky"),
        visibility = visible ? "visible" : "hidden";
    for (let i = 0; i < blinkies.length; i++) {
        blinkies[i].style.visibility = visibility;
    }
    setTimeout(function() {
        blink(!visible);
    }, 500);
})(true);

And voila! We've created a polyfill that makes the blink tag work in each browser. You can check it out in action on this demo page.

This is also a common polyfill, rather than a perfect polyfill. There are several reasons for this:

  1. Any CSS styles to blink will not have any effect. They can take effect if you instead apply the styles to blinky.

  2. This overrides implementations in browsers that already support blink, and as a corollary,

  3. this does not preserve the blink timeouts defined by the browser. For example, in Firefox, blinks are visible for three quarters of a second, and invisible for one quarter of a second. Opera might have different timeout definitions. Regardless of what the user agent has already decided, our implementation toggles between being visible and being invisible every half second.

MDN References