When I was a boot camp student, I didn’t appreciate the luxury of writing code that really only had to work for me. While I was learning, I could ignore users who would press buttons too many times or use a desktop site on mobile. And I certainly never gave a thought to browser compatibility when using a JavaScript method like includes, which isn’t supported by Internet Explorer. Now that I work on software that’s experienced on thousands of sites by millions of users, I have to put all these things into consideration. But I’m lucky again because at work, some great engineers have built out a library of polyfills for me. So rather than just take this library for granted as just another abstraction, this week I decided to look into it and see how much I could understand.
What’s a polyfill?
A polyfill is a custom method that will do the work of a familiar JS method (or a method in any other language), even if that method isn’t native to the runtime environment. While plain old array.includes(value) will error out when I’m running on Internet Explorer, if I import a method called includes from my polyfill, it’ll run custom logic written from components that are supported by every browser.
polyfills/array.js
Since I’ve mentioned includes, I decided to take a look at one particular polyfill file from my company’s codebase: array.js. This file builds an object with keys named after JavaScript methods and values that execute those methods. What I’d really like to do is go through this entire file line-by-line and break down my understanding of what each method is meant to do. But as usual, I wasn’t the author of the code and it’s part of a professional codebase, so I’ll have to refrain from sharing it all here. Still, even without looking at what the file’s functions do, there is a ton of JS that I haven’t used before. There’s plenty to learn here.
isNative
When reading the file, the first thing I came across was a custom function to determine if a provided function was native. Our polyfill’s purpose is to replace or supplement code that isn’t native to the browser, so we need some way to determine whether or not that is the case. To clarify what I mean by a function that determines if a provided function is native:
const isNative = providedFunction => {
// logic to check if the providedFunction is native
};isNative(toString); // => true
isNative(myCustomFunction); // => false
The function did two things. First, it checked to see if a provided argument was actually a function:
typeof providedFunction === 'function'
For those not familiar, the JS typeof operator will return a string describing the type of a provided operand:
typeof 'hello' === 'string'; // => truetypeof true === 'boolean'; // => true
Its second condition was more difficult to understand. It looked very similar to this (lifted off Stack Overflow):
/\{\s+\[native code\]/.test( Function.prototype.toString.call( providedFunction ) )
You might be able to intuit that the purpose of this line is to test whether providedFunction is what could be considered native code. And you’d be right! To break down what’s actually happening here, I would split this statement into three parts.
/\{\s+\[native code\]/
This is a regex statement that equates to an open curly bracket, a space, and the words ‘native code’ inside of square brackets. Why is regex required here? The simple answer is that the .test method we’re about to use is associated with regular expressions, not strings.
.test()
This method searches a provided string for the regex it’s called on. So we’re searching the argument for that regex we decoded above.
Function.prototype.toString.call( providedFunction )
I won’t go too deep into prototype because that could be a whole blog series itself, but for those who aren’t familiar, prototype is a class attribute that we can use to get or set methods associated with said class. So we’re looking at the Function class and checking the prototype for a method called toString. We can see that it’s invoked, or called, with an argument of providedFunction, which is the variable that we’ve provided.
So what happens when we call toString on a function in JavaScript? Feel free to try it out in your console:
Function.prototype.toString.call(Array.prototype.includes); // => "function includes() { [native code] }"
If you are trying this at home, remember to use a classname and prototype to test the method, rather than just the method name. We have to specify that we’re talking about the method associated with the larger class rather than a defined variable.
If we’ve done this correctly, we’ll likely see a result like what I got above…but this doesn’t really tell us much. It looks like the string indicates that we’re dealing with a function, names the function, and then gives us one more piece of information at the end. That’s right — if a function is native to the environment, when we convert it to a string it’ll include “native code” inside square brackets (clearly I didn’t write this in Internet Explorer, where includes is not native). So if we look back up at our long conditional, we can see that we’re using .test to look for some specific regex in a string that we return from calling toString on our provided function!
isNative is used throughout my codebase’s polyfill to check whether a certain function is native to a browser. If it is, it’ll just run that function on the provided object instead of invoking custom logic.
JavaScript Constants
When I think of a constant in JavaScript, I think of a variable defined with const, like this:
const name = 'Mike';
The idea here is that the name variable cannot be reassigned, which is helpful because our console will throw an error if we attempt to reassign the variable later on in our code. When I was in boot camp, I learned that const and let, which is used to declare variables that can be reassigned, were introduced to JS as part of ECMAScript 6, or ES6. Before that, JavaScript variables had to be declared using var.
Here’s what I remember about var from boot camp: it’s old and it’s dangerous (this is reductive, I know, but that’s why we all need to be constantly learning!). Variables declared with var can be reassigned really easily, and sometimes mistakenly, so it’s best to simply avoid it (scope is also important, but I didn’t understand that at the time). Use const or let depending on what you want from your variable. Of course now I sometimes read code that uses pre-ES6 convention whether it’s because the author felt more comfortable with that style of JS or because it was written before all browsers adopted ES6. The code I’m reading this week has tons of var and I noticed that some of the variable names were capitalized while others were lowercase.
var O, v;
I learned that capitalization can be a stylistic indication that a variable is not meant to be altered. This is especially useful when working with var since there is no way for us to restrict a change to a variable declared with var. A version of this convention is even useful with const in ES6 because, though a variable declared using const cannot be reassigned, its value can be changed. If we want to communicate that it is truly a constant value, we can capitalize it.
Bitwise Shift
What’s already clear to me after just a few lines of code is that checks and conditionals are very important in a function like this. Since we’re unsure of our environment and our variables, we have to be extra sure of things like data type and the existence of data before attempting to execute logic that could break our code. For example, the polyfill takes an argument of an array, which is then converted into an object using Object(). We check the length of this object as we prepare to create a while loop to iterate through its values. But there is an extra check assigned to the length variable:
var len = Object(array).length >>> 0;
It turns out that >>> is a logical right bitwise shift. Bit shifting literally moves bits contained in a value to the left or right and is often useful in creating super efficient multiplication or division operations. For a quick example:
The binary representation of the number 8 is, in 32 bits:
0000 0000 0000 1000
If we shift each bit one to the right, we get
0000 0000 0000 0100
which converted back to decimal notation is 4. Doing so again will again divide by two because 0000 0000 0000 0010 is 2.
The number to the right of the operator indicates how many places we want to move our bits. My example would have been represented as 8 >> 1 because we were moving one bit to the right. But why does the example use >> while the polyfill uses >>> ? And, in the polyfill, what’s the point of bit shifting 0 spaces?
To address the first question, >> (and << for left shift) are arithmetic shifters. In 32-bit binary notation, the very first bit is reserved for a sign, meaning negative or positive. When we shift our values to the right, that value doesn’t have anything coming its way, so it is normally filled with a 0. But that would mess with the sign of our number, so when using bitshift for arithmetic, that bit is always kept constant. If we’re dealing with -8 instead of 8, the binary will look like this:
1111111111111000
When we run an arithmetic shift one to the right, the first digit is still 1:
1111111111111100
In our case, we’re dealing with a logical shift, which does swap out the first digit for a zero. We can see the difference if we test it out in our console.
But we’re not shifting our bits at all because we’ve got a 0…so what does that mean? Based on my research, all I could determine is that this functions to convert a value into a 32-bit integer. It’ll always result in a number even if it’s being executed on a string or undefined. This can save us a lot of stress because it assures us that the variable we’re assigning will definitely be a number when we use it later.
What comes next?
Beyond that…it’s just the logic of the function. As I mentioned earlier, the file is mostly made up of a giant object that contains keys with the names of methods like every, forEach, from, and, of course, includes. Ironically the includes logic simply references another function defined in the polyfill: indexOf. Though the polyfill also defines indexOf, at work we don’t usually reference this custom definition when coding because indexOf is supported by every relevant browser including Internet Explorer. If you’re not familiar with the indexOf trick to replace includes:
const arrayToSearch = [1,2,3];
const valueToFind = 2;
arrayToSearch.indexOf(valueToFind); // => 1
// If the value doesn’t exist in the array, indexOf will return -1. // So if I want to know whether an array includes a certain value…const valueIsIncluded = arrayToSearch.indexOf(valueToFind) > -1;
Once we get our complicated/obscure syntax out of the way to assure that our function is not native and that our data types are specified, the indexOf logic in the polylfill is about what you’d expect:
- Build a while loop that runs for as long as a counter is less than the number of values in our array.
- Use the counter as an index to check, one place at a time, to see if the value exists in the array.
- Once the value is found, return the counter, or index. If we reach the end of our loop without returning any index, return -1.
Algos at work
This isn’t the first time that I saw a code pattern in the workplace that I recognized from studying algorithms. While it’s true that I personally haven’t had to write any of these yet, I think it’s important to recognize that these problems are relevant and likely become more important as an engineer builds out more code. I’m not looking forward to the first time I have to professionally write a binary search, but it’s good to know that all that studying for interviews hasn’t gone to waste and that concepts that once seemed completely random are now coming together.
Sources
- Creating JavaScript Polyfills by Steve Griffith
- Why don’t we use var anymore? by Samuel Coelho
- When to capitalize your JavaScript constants by Brandon Wozniewicz
- Left and Right Shift by Mr Powell’s Computer Science Channel
- Logical Vs. Arithmetic Shift from Open4Tech