Over the weekend I implemented a few Array methods in plain JavaScript to avoid recently patched Rhino bugs. That got my thinking about ES5 fallback implementations in various JavaScript libs/frameworks/transpilers. I decided to compile a not-so-complete list of ES5 related discrepancies found in many of them. Differences in native vs. fallback implementations create cross-browser inconsistencies and increase the chance of usage errors. I hope this post will raise awareness of just how hard it is to follow spec (during my research I found a few issues in my own projects too). All library developers should to take a closer look at their code and make the small changes needed to follow the specification (especially if your code forks for native methods).
Most implementations suffer from the following issues:
Incorrect handling of a negative fromIndex argument in Array#indexOf.
The correct implementation looks something like:
if (fromIndex < 0) {
fromIndex = Math.max(0, length + fromIndex);
}Example:
var array = ['a', 'b', 'c'];
console.log(array.indexOf('c', -1e9)); // 2 (without freezing the browser)Incorrect handling of a fromIndex argument greater than the object length minus 1 in Array#lastIndexOf.
The correct implementation looks something like:
fromIndex = Math.min(fromIndex, length - 1);Example:
var array = ['a', 'b', 'c'];
console.log(array.lastIndexOf('c', 100)); // 2Array functions like every(), filter(), forEach(), indexOf(), lastIndexOf(), map(), reduce(), reduceRight() and some() are required to skip non-existant indexes.
Example:
// make a sparse array
var array = ['a', 'b'];
array[3] = 'd';
// in IE8 press F12 to enable developer tools and console.log() support
console.log(array); // ['a', 'b', undefined, 'd'];
console.log(2 in array); // false
// Array#some should skip the non-existant index and not match an undefined value
console.log(array.some(function(value) { return typeof value == 'undefined'; })); // falseMany native methods are intentionally generic. This means that the method in question can be successfully called with its this binding set to other values.
Example:
// convert an arguments object to an array
var args = [].slice.call(arguments);
// array-like-object as its `this` binding
var object = { '0': 'a', '1': 'b', '2': 'c', 'length': 3 };
console.log([].filter.call(object, function(value, index) { return index > 1; })); // ['c']
// string as its `this` binding (in browsers that support String#charAt by index)
var str = "hello";
console.log([].map.call(str, function(value) { return value.toUpperCase(); })); // ['H', 'E', 'L', 'L', 'O']The result of Function#bind should still be able to work as a constructor.
Example:
function Alien(type) {
this.type = type;
}
var thisArg = {};
var Tribble = Alien.bind(thisArg, 'Polygeminus grex');
// `thisArg` should **not** be used for the `this` binding when called as a constructor
var fuzzball = new Tribble;
console.log(fuzzball.type); // "Polygeminus grex"OK, by a show of hands, who wants to monkey-patch a transpiler?
-
undefined in array(usesArray#indexOfinternally)- no sparse array support
-
(x) => @foo(@bar, x)(Function#bind equivalent-ish)- no support for bound functions called as constructors
- no support for curried arguments
Dojo clearly documents that it does not follow ES5 spec when it comes to sparse arrays. Unlike other libs listed, Dojo avoids cross-browser inconsistencies by opting not to use native methods.
-
dojo.every(array)- no sparse array support
-
dojo.filter(array)- no sparse array support
-
dojo.forEach(array)- no sparse array support for
array
- no sparse array support for
-
dojo.indexOf(array)- no sparse array support
- incorrect negative
fromIndexsupport
-
dojo.lastIndexOf(array)- no sparse array support
- incorrect negative
fromIndexandfromIndexgreater thanlength - 1support
-
dojo.map(array)- no sparse array support
-
dojo.reduce(array)- no sparse array support
-
dojo.some(array)- no sparse array support
-
dojo.trim(str)- no generic-this support for
str
- no generic-this support for
I'm a little harder on es5-shim because its goal is to follow the specification as closely as possible. Besides the listed implementation issues I noticed heavy use of named function expressions which will leak a super-tiny bit of memory in IE (this aggravates my dev OCD more than any real world memory consumption concerns).
-
Array#every- incorrect
ToUint32()equivalent - no sparse array support
- incorrect
-
Array#filter- incorrect
ToUint32()equivalent - no sparse array support
- incorrect
-
Array#forEach- incorrect
ToUint32()equivalent
- incorrect
-
Array#indexOf- incorrect or lack of
ToInteger()andToUint32()equivalents - incorrect negative
fromIndexsupport
- incorrect or lack of
-
Array#lastIndexOf- incorrect or lack of
ToInteger()andToUint32()equivalents
- incorrect or lack of
-
Array#map- incorrect
ToUint32()equivalent
- incorrect
-
Array#reduce- incorrect
ToUint32()equivalent
- incorrect
-
Array#reduceRight- incorrect
ToUint32()equivalent
- incorrect
-
Array#some- incorrect
ToUint32()equivalent - no sparse array support
- incorrect
-
Function#bind- doesn't curry
boundArgsfor bound function'scallandapplymethods - incorrect handling of bound functions called as constructors
- doesn't return correct value if the constructor returns a custom object value
- doesn't have checks in
callandapplyto avoid executing as a constructor if a bound instance is passed asthisArg
- doesn't curry
-
Object.defineProperty- fails to detect buggy IE 8 implementation that only works on DOM objects.
-
String#trim- using buggy character class
\sinstead of explicitly defining whitespace/line terminators
- using buggy character class
Array#indexOf- no sparse array support
jQuery.inArray()(usesArray#indexOfinternally)- no sparse array support
MooTools overwrites existing native String#trim with their own :(
-
Array#map- incorrect length set for sparse arrays with non-existant last element
-
Function#bind- no support for bound functions called as constructors
- doesn't curry
boundArgsfor bound function'scallandapplymethods
-
Object.create- Non-compliant
Object.createexists because of customFunction#createmethod (caused by the v1.2 compatibility layer)
- Non-compliant
-
String#trim- overwrites native function
- no generic-this support
Prototype overwrites existing native Array methods with their own >:(
-
Array#every- overwrites native function
- no array-like-object or generic-this support
-
Array#filter- overwrites native function
- no array-like-object or generic-this support
-
Array#indexOf- no sparse array support
- incorrect negative
fromIndexsupport
-
Array#lastIndexOf- no sparse array support
- no array-like-object or generic-this support
- incorrect
fromIndexgreater thanlength - 1support
-
Array#map- overwrites native function
- no sparse array support
- no array-like-object or generic-this support
-
Array#reverse- overwrites native function
- no array-like-object or generic-this support support when custom
inlineargument is passed
-
Array#some- overwrites native function
- no array-like-object or generic-this support
-
Function#bind- overwrites native function
- no support for bound functions called as constructors
- doesn't curry
boundArgsfor bound function'scallandapplymethods
-
String#strip(like String#trim)- no generic-this support
-
Array#forEach- no sparse array support
-
Array#every- no sparse array support
-
Array#filter- no sparse array support
-
Array#indexOf- no sparse array support
- incorrect negative
fromIndexsupport
-
Array#lastIndexOf- no sparse array support
- incorrect
fromIndexgreater thanlength - 1support
-
Array#map- no sparse array support
-
Array#reduce- no sparse array support
- incorrect explicitly passed
initialValueasundefinedsupport
-
SC.Array#slice- incorrect
beginIndexgreater thanlengthsupport - incorrect negative
beginIndexandendIndexsupport
- incorrect
-
Array#some- no sparse array support
-
_.bind(func)- no support for bound functions called as constructors
- doesn't curry
boundArgsfor bound function'scallandapplymethods
-
_.each(obj)- no generic-this support for
obj
- no generic-this support for
-
_.every(obj)- no generic-this support for
obj
- no generic-this support for
-
_.filter(obj)- no generic-this support for
obj
- no generic-this support for
-
_.indexOf(obj)- no sparse array support
-
_.lastIndexOf(obj)- no sparse array support
-
_.map(obj)- no generic-this support for
obj - incorrect length set for sparse arrays with non-existant last element
- no generic-this support for
-
_.reduce(obj)- no generic-this support for
obj - no support for explicitly passing an
undefinedvalue forinitialValue(restricts internal native Array#reduce calls for consistency)
- no generic-this support for
-
_.reduceRight(obj)- no generic-this support for
obj - no support for explicitly passing an
undefinedvalue forinitialValue(restricts internal native Array#reduceRight calls for consistency)
- no generic-this support for
-
_.some(obj)- no generic-this support for
obj
- no generic-this support for
I decided to include Valentine to show that libs new and old are affected by ES5 compliance issues.
-
v.bind()- no support for bound functions called as constructors
- doesn't curry
boundArgsfor bound function'scallandapplymethods
-
v.each(a)- no generic-this support for
a
- no generic-this support for
-
v.every(a)- no array-like-object or generic-this support for
a
- no array-like-object or generic-this support for
-
v.filter(a)- no array-like-object or generic-this support for
a
- no array-like-object or generic-this support for
-
v.indexOf(a)- no array-like-object or generic-this support for
a - no support for a negative
fromIndex
- no array-like-object or generic-this support for
-
v.lastIndexOf(a)- no array-like-object or generic-this support for
a fromIndexincorrectly set tolengthinstead oflength - 1when it's greater thanlength - 1
- no array-like-object or generic-this support for
-
v.map(a)- no generic-this support for
a - incorrect length set for sparse arrays with non-existant last element
- no generic-this support for
-
v.some(a)- no array-like-object or generic-this support for
a
- no array-like-object or generic-this support for
-
v.trim(s)- no generic-this support for
s
- no generic-this support for
-
Y.Array.each(a)- no sparse array support for
a - no generic-this support for
a
- no sparse array support for
-
Y.Array.indexOf(a)- no sparse array support for
a - no generic-this support for
a
- no sparse array support for
-
Y.Array.some(a)- no sparse array support for
a - no generic-this support for
a
- no sparse array support for
-
Y.Lang.trim(s)- no generic-this support for
s
- no generic-this support for