Click here to Skip to main content
15,868,292 members
Articles / Programming Languages / Javascript

Writing Polyfills in JavaScript

Rate me:
Please Sign up or sign in to vote.
4.83/5 (4 votes)
24 Apr 2012CPOL5 min read 77.9K   10   3
There are many different implementations of JavaScript that disagree on which features they support. Writing polyfills bridges the gap between implementations and allows you to code as if every browser supported the feature.

Introduction

JavaScript is notorious for having cross-browser compatibility issues. Internet Explorer, Mozilla Firefox, Google Chrome, Safari and Opera all have their own proprietary features and their own subset of the standard features. Different browsers implement each feature in their own way, and this can be a major headache for web developers trying to make things work for everybody.

Fortunately, JavaScript has enough flexibility and extensibility to bridge some of these gaps between each browser. These bridges are called polyfills.

We will create a few simple polyfills to see how they work. In this article, I will assume that you are a developer with a basic understanding of how JavaScript works.

What is a Polyfill?

A polyfill is a piece of code that implements the features that you expect the browser to support natively.

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.

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.

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
var 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
var fruits = {
  banana: "yellow",
  strawberry: "red",
  pumpkin: "orange",
  apple: "red"
};
var 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;
  var result = [];
  for (var i = 0; i < this.length; i++) {
    if (i in this) {
      var 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
var arr = [0, 1, 2];
for(var i in arr) {
  console.log(i);
}

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

Second Polyfill Example: Blink Tag Support

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() {
  var blinks = document.getElementsByTagName("blink");
  while (blinks.length) {
    var blink = blinks[0];
    var 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) {
    var blinkies = document.getElementsByTagName("blinky"),
        visibility = visible ? "visible" : "hidden";
    for (var 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.

That's All, Folks!

If you are interested in seeing more polyfills, you should take a look at this wiki maintained by the Modernizr community.

History

  • 24th April, 2012: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I am active on Stack Overflow.

You can contact me via email at peter.e.c.olson+codeproject@gmail.com

Comments and Discussions

 
QuestionWho makes up this names? Pin
HaBiX24-Apr-12 23:08
HaBiX24-Apr-12 23:08 
AnswerRe: Who makes up this names? Pin
Peter_Olson25-Apr-12 2:51
Peter_Olson25-Apr-12 2:51 
I don't know who made up the name, but it makes sense. "poly" because it is targeted to many browsers, and "fill" because it fills in missing features.
GeneralRe: Who makes up this names? Pin
HaBiX25-Apr-12 21:12
HaBiX25-Apr-12 21:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.