40 Polyfill Using Polyfill to Provide Native Browser Support

40 Polyfill Using Polyfill to Provide Native Browser Support #

Hello, I am Ishikawa.

In previous lessons, we mentioned that programs written in JavaScript do not run in a unified environment. Although we know that there are organizations like TC39 that define the ECMAScript specification, and the W3C (World Wide Web Consortium) that write HTML5 and CSS specifications, the implementation of JavaScript virtual machines by different browser vendors can affect the results of our programs when they are finally executed. This is because even though the standards exist, the companies that implement these standards operate independently from the standards, which leads to different implementations under the interpretation of different browser vendors and JS engines.

Today, let’s take a look at how to address the issue of varying levels of support for a group of newer JavaScript and related Web API features by different browser vendors. We can solve this problem by creating a polyfill, which can be understood as a patch in English.

Causes of native support issues #

First, let’s take a look at why browsers have native support issues.

Native support issues have always plagued developers, especially in the early days of internet applications, when the most common native support problems were with IE. This is mainly due to several reasons:

  • First, early versions of IE did not have automatic updates, and most of us did not have the habit of manually updating software. This resulted in many users sticking to older versions of the browser even when new versions of IE provided more new features and bug fixes.
  • Second, because Windows systems were not bundled with computers in the early days, many people used cracked versions of Windows. In order to avoid affecting the use of pirated versions, many people would not actively update the operating system, which further delayed the updating of the browser versions associated with the system.
  • Third, IE used in enterprises was also a major area of browser compatibility issues. The larger the company, the more “stability” takes precedence, so IT administrators are needed to centrally update the computer operating system and software versions. As a “gateway” software that connects to the internet, browsers are particularly focused on security protection and require centralized updating, which greatly slows down the speed of browser updates. Therefore, in the early stages of website and web application development, the main issues were focused on the lack of support for new versions of JavaScript in IE6+.

Nowadays, with IE phased out, backward compatibility issues are gradually decreasing, but the problems are not completely eliminated all at once. Besides IE, other browser vendors such as Chrome, Safari, Mozilla, and Opera are also companies. Since they are companies, there is competition among them to gain more market share.

Because of this, they sometimes add their own interpretations when the newly defined JavaScript rules are still relatively ambiguous, instead of all browsers using the same set of new feature release cycles and standards. Some companies believe that when new features appear in the JavaScript specification, they understand better how to implement them, allowing developers to experience new features in advance. This also helps to validate the standards. On the other hand, some browsers do not always support some forward-looking features.

Methods to resolve native support issues #

So when faced with the lack of consistency in browser support for new features, there are two methods available if we want to use newer JavaScript features:

  • The first method is to use Babel.js, as mentioned earlier. By transpiling JavaScript, we can use newer features while ensuring support for older browser versions.
  • The second method is to use Polyfills. Polyfills act as plugins that provide newer browser functionalities and also support older versions.

Earlier, we discussed Babel.js as a tool for code transpiling. Can we use a similar tool to resolve all native support issues?

It should be noted that Babel’s main ability is to allow us to use new JavaScript syntax and features such as variables (let, const). However, it can’t transpile certain new features in JavaScript. For example, it can’t support the map method on arrays or transpile promises. This is when we need to combine the use of Polyfills.

So when should we use Polyfills and when can we use transpilers?

Although there isn’t a clear boundary, there is a general principle that we can refer to: If you want to use new JavaScript syntax in advance, you need to use a transpiler; but if you want to implement a new functionality or method, you might need to use a Polyfill.

It’s also worth noting that although Polyfills are written in JavaScript and run in JavaScript engines, they aren’t limited to just JavaScript patching. They can also be used to address native support problems in HTML and CSS. Many of the Polyfills we manually write today are actually patches related to HTML and CSS. This is because Babel, as a tool that combines JavaScript transpiling with Polyfills, has already solved most of the syntax and functionality issues.

In the previous lesson on Babel, we mentioned that ECMAScript 6 is the major update to JavaScript, introducing a lot of new features and syntax. Therefore, if you want to support Internet Explorer or use the latest ECMAScript features in other browsers that don’t support ES6, using Babel can systematically address these issues. However, when it comes to HTML5 and CSS3, there isn’t a unified tool like Babel available on the market because there are too many related functionalities, and many of them aren’t always needed in programs. As a result, individual patches targeting specific tasks have emerged.

In today’s constantly evolving landscape of standards and browsers, keeping track of the ever-growing support for HTML5 and CSS can be a very challenging task. For example, if we want to use CSS animations to create effects on a webpage and want to know which browsers support this feature, or how to handle browsers that don’t understand such code, these issues can be quite challenging. Fortunately, to address this problem, there is a website called html5please.com that provides a comprehensive list of HTML5 elements and CSS3 rules, outlines browser support, and lists any Polyfills available for each element.

Implementation of Polyfill #

Since our focus is on JavaScript, let’s use a JS example to see how to solve JavaScript native support issues through Polyfill. The operation mechanism of Polyfill is very simple. When certain features are not supported by the browser, it provides a backward compatible way.

When writing a Polyfill, it usually follows a pattern: we first determine whether the function we want to implement has already been supported by the current browser runtime. If it is supported, we don’t need to use Polyfill; if not, we need the JavaScript engine to execute the patches we define. Now, let’s write a Polyfill to better understand how it works.

We can take the forEach method in an array as an example. First, let’s look at the definition and usage of forEach. In terms of definition, when we use the forEach method on an array, we need to pass in a callback function. The callback function has three parameters, the first one being mandatory, representing the current element’s value; the second one is optional, representing the index of the current element in the array; the third one is optional as well, representing the array object to which the current element belongs. The first two parameters are commonly used, so they are the focus of our attempt at implementation.

array.forEach(function(currentValue, index, arr), thisValue)

Now let’s look at the purpose of the forEach method. First, let’s create an array called oldArray and add three place names to it: New York, Tokyo, and Paris. Then let’s create an empty array called newArray. We can push the elements inside the first array to the second array by iterating through the first array. This is a simple use case of forEach.

var oldArray = ["New York", "Tokyo", "Paris"];
var newArray = [];

oldArray.forEach( function(item, index) {
  newArray.push(index + "." + item);
}, oldArray);

newArray; // ['0.New York', '1.Tokyo', '2.Paris']

When writing a Polyfill, we usually check whether the feature itself has already been supported. To determine whether the current browser we are using supports the forEach method, we can use the console in the developer tools to write a piece of code to test it.

We can check whether the forEach method exists on the prototype of an array by checking if Array.prototype.forEach is undefined. If it returns undefined, it means the current browser does not support this method, i.e., the browser has no idea what it is; if it returns true, it means forEach is not equal to undefined, so forEach is natively supported in my current version of the browser.

Array.prototype.forEach !== undefined;

Because the forEach method has been supported since ES6, if you run the above code, you will most likely get true, meaning that you should see the browser’s support for the forEach method. However, to try the process of hand-writing a Polyfill, let’s assume that the current browser is too old and does not know about the forEach function or define the forEach method, so it does not exist in the current browser we are using. Now let’s manually write a patch to implement this functionality.

Now, we can officially write the functionality of the Polyfill. First, when building this Polyfill, the first step is to check whether the passed-in parameter is a function. Since we want this Polyfill to be used on all array prototypes, we need to access the Array object in JavaScript and define a new function method called forEach on its prototype.

One thing to note here is that because our current browser supports the forEach function, doing this is actually equivalent to overriding the natively supported forEach function. So first, we need to check if the user’s input is a function parameter. We can use the built-in typeof method in JavaScript to check the type of the parameter. If the result is not a function, the program should throw an error. This is actually a type check.

Array.prototype.forEach = function(callback, thisValue){
  if (typeof(callback) !== "function") {
    throw new TypeError(callback + " is not a function");
  }
  var arrayLength = this.length;
  for (var i=0; i < arrayLength; i++) {
    callback.call(thisValue, this[i], i, this);
  }
}

var oldArray = ["New York", "Tokyo", "Paris"];
var newArray = [];

oldArray.forEach( function(item, index) {
  newArray.push(index + "." + item);
}, oldArray); 

newArray; // ['0.New York', '1.Tokyo', '2.Paris']

If the parameter passes the type check, then we can proceed to write the implementation part of forEach.

In this part, we first need to obtain the length of the array. Since this represents the subject when the function is called, i.e., we are calling the forEach method through an array, we can use this.length here to get the length of the array.

Then, we can use a for loop to iterate through the elements in the array and execute the callback function for each element. In the callback function, we can pass in the current element value, the index of the element, and the array as the three parameters. Here, to ensure that this is correctly referenced, we use the call() method, so that this points to the correct second parameter object we passed in.

Similarly, we can test with the previous example, and you will find that the Polyfill we used to override the original forEach function achieved the same effect.

Summary #

Through the study of this lecture, we have learned how to use Polyfill to solve problems that are not natively supported and understand the differences and complementarity between Polyfill and transpilation tools. If we only use them without understanding how they work, these two tools can easily be confused. So here I want to emphasize again that a deeper understanding is necessary. The common goal of both transpilation and Polyfill is to provide native support, but the difference lies in the content supported and the implementation method.

  • Transpilation mainly solves the problem of native support for syntax and it achieves this by converting source code into source code, rather than machine code.
  • Polyfill mainly solves the problem of native support for functionality and it achieves this by adding patches to the object prototype to support the relevant functionality.

Transpilation and Polyfill are not mutually exclusive concepts. On the contrary, they are highly complementary and can be used together in most cases.

I believe many students have used Polyfill before, and I hope that now you not only know how to use it, but also have the ability to design and write a Polyfill.

Reflection Exercise #

Have you ever used Polyfill in your development work? What problems do you mainly use it to solve?

Please feel free to share your experiences, discuss your learning insights, or ask questions in the comments section. If you found this content helpful, you are also welcome to share it with more friends. See you in the next class!