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 couple of 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)); // 2
Array 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'; })); // false
Many 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#indexOf
internally)- no sparse array support
-
(x) => @foo(@bar, x)
(Function#bind equivalent)- 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
fromIndex
support
-
dojo.lastIndexOf(array)
- no sparse array support
- incorrect negative
fromIndex
andfromIndex
greater thanlength - 1
support
-
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 - no generic-this support
- incorrect
-
Array#indexOf
- incorrect or lack of
ToInteger()
andToUint32()
equivalents - incorrect negative
fromIndex
support - no generic-this support
- incorrect or lack of
-
Array#lastIndexOf
- incorrect or lack of
ToInteger()
andToUint32()
equivalents - no generic-this support
- incorrect or lack of
-
Array#map
- incorrect
ToUint32()
equivalent - no generic-this support
- incorrect
-
Array#reduce
- incorrect
ToUint32()
equivalent - no generic-this support
- incorrect
-
Array#reduceRight
- incorrect
ToUint32()
equivalent - no generic-this support
- incorrect
-
Array#some
- incorrect
ToUint32()
equivalent - no sparse array support
- incorrect
-
Function#bind
- 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
call
andapply
to avoid executing as a constructor if a bound instance is passed asthisArg
- incorrect handling of bound functions called as constructors
-
Object.defineProperty
- fails to detect buggy IE 8 implementation that only works on DOM objects.
-
String#trim
- using buggy character class
\s
instead of explicitly defining whitespace/line terminators
- using buggy character class
Array#indexOf
- no sparse array support
jQuery.inArray()
(usesArray#indexOf
internally)- no sparse array support
MooTools overwrites existing native String#trim with their own :'(
-
Array#every
- no generic-this support
-
Array#filter
- no generic-this support
-
Array#indexOf
- no generic-this support
-
Array#map
- no generic-this support
-
Array#some
- no generic-this support
-
Function#bind
- no support for bound functions called as constructors
-
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
- no generic-this support
- incorrect negative
fromIndex
support
-
Array#lastIndexOf
- no sparse array support
- no array-like-object or generic-this support
- incorrect
fromIndex
greater thanlength - 1
support
-
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
inline
argument 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
-
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
fromIndex
support
-
Array#lastIndexOf
- no sparse array support
- incorrect
fromIndex
greater thanlength - 1
support
-
Array#map
- no sparse array support
-
Array#reduce
- no sparse array support
- incorrect explicitly passed
initialValue
asundefined
support
-
SC.Array#slice
- incorrect
beginIndex
greater thanlength
support - incorrect negative
beginIndex
andendIndex
support
- incorrect
-
Array#some
- no sparse array support
-
_.bind(func)
- no support for bound functions called as constructors
-
_.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
- no generic-this support for
-
_.reduce(obj)
- no generic-this support for
obj
- no support for explicitly passing an
undefined
value 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
undefined
value 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
-
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
fromIndex
incorrectly set tolength
instead oflength - 1
when it's greater thanlength - 1
- no array-like-object or generic-this support for
-
v.map(a)
- no generic-this support for
a
- 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