The 5th edition of ECMA-262 standard aka ES5 introduced really useful methods of the built-in Array object : forEach, every, some, filter, map, reduce, reduceRight. The only problem is that they may not be present in all implementations of the standard, for example in MS IE8 implementation that is based on the 3rd edition of the ECMA-262. Fortunately we can work around this by inserting the polyfill for these methods in our scripts. It’s all well and good, but a polyfill’s code might look a little bit odd, for example here is the slightly modified MDN implementation of forEach method:
The loop on lines 13–18 seems a little bit strange, doesn’t it? On the surface it may be unclear why the implementation isn’t as simple as something like this:
But it turns out that this “simple” polyfill may cause significant performance penalty because ECMAScript lets developers create “sparse” arrays that have arbitrary length, just like this:
The “simple” forEach polyfill called on a1, a2 or a3 array will invoke callback function 1B+1 times including 1B redundant calls for the elements of the array which do not actually exist, for example a1…a1[1000*1000*1000–1] are missing and return undefined value. Not only it could mislead the callback’s code if it handles undefined values, for example counts how many of them exist in a given array, but it also may significantly increase the running time of a code. So this is completely wrong way to implement the polyfill of forEach method, moreover, you better be vigilant about using traditional for(var i=0;i<a.length;i++) loop for traversing arrays that could come up to your script from an external, potentially harmful code. Fortunately the ES5 standard explicitly specifies how forEach, map, filter, etc solve this problem:
callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.
The MDN polyfill gracefully implements this requirement by leveraging the distinctive feature of Array objects properties:
Array objects give special treatment to a certain class of property names. A property name P (in the form of a String value) is an array index if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 232−1.
In other words if an array has property with name “42” then the element with index 42 does exist in the array and vice versa. The properties are adjusted automatically when an array is being modified. Thus, in operator that is called in line 14 of the MDN polyfill checks whether an element with index k actually exists in an array. Here is the descriptive example of using this approach:
So far, so good, but here is another interesting observation : the polyfill does not check if it was called on the actual array, moreover the specification says that forEach, map, filter, etc. function:
is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.
Technically, a method of one object can be “transferred” to another object by using Function.prototype.call. The most well-known application of this feature is calling Array methods on Array-like objects:
arguments object that corresponds to the parameters passed to a function is not an Array, but it has length property and indexed elements, hence Array.prototype.filter function can perfectly handle it. Note that in the code above filterFn.call returns the new Array object, so the subsequent forEach call does not require transferring.
Another example of Array-like object is HTMLCollection:
And one more trick: ECMAScript 5 introduced the way to treat the string as an array-like object, where individual characters correspond to a numerical index. You probably use this feature all the time, but take a look at how it can be leveraged to reverse a string, I guess that you probably didn’t see anything like that before: