Back

All About JavaScript Polyfills

All About JavaScript Polyfills

What are polyfills in JavaScript, how do you build or use them, and why may you need them? This article replies all these questions.

If you have been working with JavaScript long enough, you must have heard of the concept of polyfills and most likely thought little of the subject. You might also be aware that updates are made to JavaScript almost yearly, and not all browsers support the latest updates. At some point, your website has probably had features working on one browser but not on another, which can be very frustrating. After scouring the internet for solutions, you eventually fix the bug with a random JavaScript snippet. You deliver the project flawlessly, but at the end of the day, you are left asking the big question: What are polyfills? Hopefully, that question has led you to today’s article, where we will answer what polyfills are and how they work.

What are Polyfills?

Polyfills are pieces of code that provide modern functionality on older browsers that do not natively support it. They act as a fallback, enabling developers to use new JavaScript features on older browsers that do not support them or even on modern browsers that have yet to implement those new JavaScript features. This tact ensures that web applications work across different browser versions. Although the concept exists in other languages like Python and PHP, it is most commonly associated with JavaScript.

Now that we have a basic understanding of polyfills, let’s clarify a few more things. We have mentioned that newer browsers support some JavaScript features that are not supported on older browsers. We have also implied in the introductory excerpt that JavaScript is still a growing language and receives updates occasionally. Let us merge these two ideas by briefly discussing ECMAScript.

ECMAScript (ES) is the standard upon which JavaScript is based. It defines the syntax, types, and various built-in objects that form the core of the language. New versions of ECMAScript are released regularly, introducing new features and improvements and fixing issues with previous versions. Now, when these updates are released, it is up to the big companies who created and maintain our favorite browsers like Chrome and Mozilla and other not-so-loved browsers like Safari to update their browser engine with the implementation to support the updated Javascript syntax and objects and what not. Often, this change is made only to the later versions of these browsers; hence, support for the updated features is unavailable for older browsers. In other scenarios, support is implemented quicker on some browsers than others (Safari is a popular culprit here). Now, front-end developers want to cater to as many users as possible, irrespective of their choice of browser or its version. To do this, we create and utilize polyfills, which often work by using the much older ECMAScript specification that formed the core of JavaScript, which can run on most older browsers to implement the same feature or something similar. In summary, we need polyfills in JavaScript to provide backward compatibility for our programs and make them accessible to as many users as possible.

How Polyfills Work

Now that we know a polyfill is just old code that performs new tricks, we can imagine a ton of polyfills are being used on many web applications today. Multiple polyfill implementations often aim to achieve the functionality of just one particular new feature, with some working better than others depending on the use case. However, as varied as they are, the general idea of their operation is pretty standard—any polyfill implementation works on these basic three principles: Feature Detection, Conditional Loading, and Functionality Implementation.

Let us begin by looking at feature detection. As the name implies, it has to do with detecting whether the said feature replicated by the polyfill is available on the user’s browser type and version. This concept is an essential aspect of creating polyfills because it dramatically reduces redundancy, especially considering that, in most cases, the user will be on a supported browser version, and there will be no need to use your custom implementation for the intended feature. In cases where we do not use feature detection, not only do we introduce redundancy, but in cases where our polyfill is not as perfect as the new JavaScript implementation, we will drastically reduce the quality of our user experience, so always start your polyfills with feature detection. 

The next step is Conditionally Loading. There is not much to say here. Depending on whether the feature is detected on the user’s browser or not, you will decide to load or not to load your script. The thought process here questions how best to load your script. A good tip here would be to load the script asynchronously if the functionality your polyfill provides is not essential to the initial rendering of your web application.

Finally, the last step is the actual implementation of your polyfill’s unique functionality. Since the goal is to ensure some backward compatibility for browsers, any code you write while implementing the new function should be meticulously vetted to use only methods and objects native to the core JavaScipt syntax. Usually, ES5 or prior is a safe bet. Lastly, do not forget to test your polyfill on the target browsers to ensure it meets expectations.

Common Use Cases for Polyfills

With each new ECMAScript specification, more use cases for polyfills emerge. However, certain use cases come to mind quicker than others when discussing polyfills. The more commonly poly-filled methods and APIs are discussed below.

  • JavaScript Methods: This includes everything from popular Array methods like Array.prototype.includes and Array.prototype.find to String methods like String.prototype.startsWith and String.prototype.endsWith to even Object methods like Object.assign and Object.entries. These newer methods, and many others not mentioned that are now often used in today’s JavaScript, were introduced in later versions of the ECMAScript specification and have several readily available polyfill options on the web.
  • Promises (Asynchronous Programming): Promises, introduced in ECMAScript 2015 (ES6), are a fundamental part of modern JavaScript for handling asynchronous operations. Many older browsers do not support promises, hence necessitating a polyfill. Fetch API: The fetch API is a modern replacement for the older XMLHttpRequest for making network requests, and it is an efficient one at that. It is so well implemented that most newer developers today do not even know it was not the original implementation and is not supported in older browsers. A polyfill is often used to use this crucial API on older browsers.
  • Intersection Observer API: The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or a top-level document viewport. This has become a common API used especially while trying to implement an infinite loading page on-scroll, a popular UI choice in web applications today. Several Polyfills have been written to try and emulate this functionality, and it has been notably difficult to ensure the same consistency in performance as the actual API. HTML5 Form Validation: HTML5 introduced some features to the HTML syntax, including new input types (e.g., email, URL, number) and validation attributes (e.g., required, pattern). Polyfills can help older browsers recognize and correctly handle these features on our websites, enabling a consistent form validation experience across all browsers.
  • CSS Grid and Flexbox: CSS grid and flexbox features are powerful layout systems that have revolutionized web design. The modern front-end developer is a master of both as they offer a quick and efficient approach to positioning and manipulating the visuals of our web applications. However, they are a relatively new feature as far as today’s topic is concerned, and therefore, not all browsers support these features, or they may support them with inconsistencies. Polyfills are available that provide a fallback for browsers that do not fully support these CSS features, ensuring that web layouts appear as intended across different browsers.

Polyfills come into play in so many scenarios that they are very likely a part of your code base, even if you do not write it yourselves. Some standard library or dependency in your application probably implements it as a fallback so that its performance is uniform across browsers. Hence, you do not necessarily need to know when each method or object used in your code base was added to the JavaScript specification. You should not spend all your time writing polyfills for your MVP applications either. When the need to write a polyfill comes up, it will usually be glaring enough for you to notice. However, if you are particularly curious about a certain JavaScript feature, the compatibility section on the MDN documentation for the said feature should be of help.

CTA_Middle_Languages />

Demo Implementation of a Simple Polyfill

In this section, we will use our knowledge of how polyfills work, as discussed in earlier sections, to analyze two popular polyfill implementations for the Array.prototype.includes method, which was introduced with ECMAScript in 2016. This array method simply checks the elements in an array to see if it contains a given element and returns true or false based on the result of the operation. For this demo, we will have two implementations of the polyfills to show that we can actually have multiple implementations of the same functionality in polyfills and that one implementation may work better than another in a particular case.

Using a for loop

For the first implementation, we will look at a low-level implementation of the array method using one of the most basic syntaxes in any programming language, a for loop. This approach is adapted from the kelvatus/polyfill-array-includes GitHub repo which itself is based on the Array.prototype.includes MDN documentation.

// Check if the includes method exists on the Array Object in the browser
if (!Array.prototype.includes) {
  Array.prototype.includes = function(element, fromIndex) {
    //Check for null or undefined array context
    if (this == null) {
      throw new TypeError('"this" is null or not defined');
    }
    //Convert the context object to an array-like object
    var o = Object(this);
    //get the length of the array as a positive integer
    var len = o.length >>> 0;
    if (len === 0) {
      return false;
    }
    //define the starting index
    var n = fromIndex | 0;
    var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
    // loop through the array to check for the expected element, while doing extra checks for the quirky NaN comparison
    while (k < len) {
      if (o[k] === element || (element !== element && o[k] !== o[k])) {
        return true;
      }
      k++;
    }
    return false;
  };
}

Now, that is some ancient JavaScript code! If you find it complicated, move ahead for now and maybe come back later to understand it; the comments are there to help. To avoid deviating from the purpose of this article into explaining older JavaScript syntax, let us move along and take a high-level overview of the code block instead. The code starts by checking if the includes method exists on the Array object. This is feature detection in action. If the includes method does not exist, it creates one and adds it to the Array object. This will be the conditional loading step of our polyfilling process. Finally, the actual implementation of the functionality begins.

Using indexOf Method

This next implementation is much simpler and uses an existing array method, indexOf, from an older ECMAScript specification-ES5 to create a functional clone of the includes method.

if (!Array.prototype.includes) {
  Array.prototype.includes = function(element) {
    return this.indexOf(element) !== -1;
  };
}

And we are done. Much easier, right? At first, the approach here is similar to the initial implementation, beginning with feature detection and conditional loading. However, the implementation of the actual functionality varies. This approach is much shorter than the first because we are building upon an already existing array method-indexOf, which already has most of the checks and edge cases of working with Array Object done by default. We can only do this because the logic behind indexOf is very similar to what we want from the includes method. The indexOf method returns the index of the first occurrence or instance of an element in an array or -1 if the element is not found. The includes, on the other hand, returns true or false depending on whether the array contains the queried element, hence all we needed to do to get indexOf to function like includes was to return true if any number other than -1 was found and false if -1 was returned.

Now, for a quick comparison of the two approaches. Approach one using the for loop is definitely more complex and longer. However, it is going to support a more older browser than approach two with indexOf because it uses a much older version of the ECMAScript (ES3). The second approach is, however, much easier to write and, surprisingly, will show better performance than the first because it uses a native method on the array, which has been modified to work better with the underlying JavaScript engine. Depending on the use case, you can select the more appropriate one.

Where to Get the Best Polyfills

When integrating polyfills into your JavaScript projects, several popular libraries and resources provide reliable solutions for ensuring cross-browser compatibility, saving you the stress of writing it yourself. Unless you have very specific needs for your project, you are almost always better off using these libraries and resources as their implementations are well-tested across the developer community.

Here are some of the most trusted sources for finding and using polyfills, from independent polyfill libraries, which you can easily add as a script in your code, to popular websites where experienced developers have come together to create some of the best polyfills we have today.

  • Polyfill.io Polyfill.io is a service that automatically delivers polyfills for the features that your browser lacks based on a query of the current browser’s capabilities. It efficiently serves only the polyfills needed by the browser for your application, thereby reducing unnecessary downloads and improving overall application performance. As of the time of writing, Polyfill.io is currently under scrutiny for introducing a security leak into its dependent applications and should not be used till further notice.

  • Core-js Core-js is an alternative to Polyfill.io. It is a modular standard library for JavaScript that includes polyfills for ECMAScript features and other utilities. It provides polyfills for a wide range of JavaScript methods and APIs, ensuring compatibility across different browser versions.

  • MDN Web Docs MDN Web Docs, unlike the technologies above, is not a library. It is a website that provides comprehensive documentation on web technologies, including JavaScript methods and APIs, which virtually all web developers use and love. They often include polyfill code snippets alongside explanations of browser compatibility for various methods and web API, which you can easily grab and integrate into your application.

  • GitHub Polyfills GitHub hosts various repositories dedicated to polyfills for specific JavaScript features. Feel free to explore these repositories to get whatever polyfill your application might need. On the other hand, you can choose to contribute to existing polyfill projects. That will be much appreciated. It should be mentioned that polyfills from less popular repositories should be used cautiously, as they may not be as well tested or effective.

  • Babel Babel is primarily known as a JavaScript compiler that allows developers to use the latest JavaScript features and compile it to a preferred version of the ECMAScript, which by default (if compiled to an older version) may provide some backward compatibility to older browsers. It also includes an option to enable polyfills for features not natively supported by the target environment.

Choosing the Right Polyfill

When selecting a polyfill for your project, remember to consider the following factors:

  • Browser Support: Ensure the polyfill supports your target browsers. Testing the behavior of your polyfill on said browser version or browser types should be an essential step of your polyfilling process. Performance: I prefer lightweight polyfills that implement feature detection and only include necessary features to minimize the impact on page load times.
  • Maintenance: Choose polyfills from active projects with recent updates and community support.

By leveraging these resources and considerations, you can effectively manage cross-browser compatibility issues and enhance the user experience across diverse browser environments. All the libraries or sites mentioned in this section are a good way to begin searching for the perfect polyfill. Information on how to use the libraries is available on their respective websites or repositories.

Summary

In this article, we have gone over what polyfills are, how they work, how to write one and where to get the rest. Polyfills will always play a role in modern JavaScript. At this point, it is proof of the evolution of the language. As more and more features are added to JavaScript to cater to the ever-increasing complexity and capabilities of our browser, polyfills will always come along to make sure no one is left behind.

References

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay