Skip to content

Instantly share code, notes, and snippets.

@crossai-2033
Created August 9, 2011 03:39
Show Gist options
  • Save crossai-2033/1133358 to your computer and use it in GitHub Desktop.
Save crossai-2033/1133358 to your computer and use it in GitHub Desktop.
Incorrect ES5 fallbacks

Incorrect ES5 fallbacks

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).

Common Issues

Most implementations suffer from the following issues:

Buggy Array#indexOf

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)

Buggy Array#lastIndexOf

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

Lack of sparse array support

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

Lack of array-like-object / generic-this support

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']

Bound functions as constructors

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"

The Breakdown

CoffeeScript v1.1.2

OK, by a show of hands, who wants to monkey-patch a transpiler?

  1. undefined in array (uses Array#indexOf internally)

    • no sparse array support
  2. (x) => @foo(@bar, x) (Function#bind equivalent)

    • no support for bound functions called as constructors
    • no support for curried arguments

Dojo v1.6.1

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.

  1. dojo.every(array)

    • no sparse array support
  2. dojo.filter(array)

    • no sparse array support
  3. dojo.forEach(array)

    • no sparse array support for array
  4. dojo.indexOf(array)

    • no sparse array support
    • incorrect negative fromIndex support
  5. dojo.lastIndexOf(array)

    • no sparse array support
    • incorrect negative fromIndex and fromIndex greater than length - 1 support
  6. dojo.map(array)

    • no sparse array support
  7. dojo.reduce(array)

    • no sparse array support
  8. dojo.some(array)

    • no sparse array support
  9. dojo.trim(str)

    • no generic-this support for str

es5-shim v1.2.4

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).

  1. Array#every

    • incorrect ToUint32() equivalent
    • no sparse array support
  2. Array#filter

    • incorrect ToUint32() equivalent
    • no sparse array support
  3. Array#forEach

    • incorrect ToUint32() equivalent
    • no generic-this support
  4. Array#indexOf

    • incorrect or lack of ToInteger() and ToUint32() equivalents
    • incorrect negative fromIndex support
    • no generic-this support
  5. Array#lastIndexOf

    • incorrect or lack of ToInteger() and ToUint32() equivalents
    • no generic-this support
  6. Array#map

    • incorrect ToUint32() equivalent
    • no generic-this support
  7. Array#reduce

    • incorrect ToUint32() equivalent
    • no generic-this support
  8. Array#reduceRight

    • incorrect ToUint32() equivalent
    • no generic-this support
  9. Array#some

    • incorrect ToUint32() equivalent
    • no sparse array support
  10. 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 and apply to avoid executing as a constructor if a bound instance is passed as thisArg
  11. Object.defineProperty

  12. String#trim

ExtJS v3.1.0

  1. Array#indexOf
    • no sparse array support

jQuery v1.6.2

  1. jQuery.inArray() (uses Array#indexOf internally)
    • no sparse array support

MooTools v1.3.2

MooTools overwrites existing native String#trim with their own :'(

  1. Array#every

    • no generic-this support
  2. Array#filter

    • no generic-this support
  3. Array#indexOf

    • no generic-this support
  4. Array#map

    • no generic-this support
  5. Array#some

    • no generic-this support
  6. Function#bind

    • no support for bound functions called as constructors
  7. String#trim

    • overwrites native function
    • no generic-this support

Prototype v1.7.0

Prototype overwrites existing native Array methods with their own >:(

  1. Array#every

    • overwrites native function
    • no array-like-object or generic-this support
  2. Array#filter

    • overwrites native function
    • no array-like-object or generic-this support
  3. Array#indexOf

    • no sparse array support
    • no generic-this support
    • incorrect negative fromIndex support
  4. Array#lastIndexOf

    • no sparse array support
    • no array-like-object or generic-this support
    • incorrect fromIndex greater than length - 1 support
  5. Array#map

    • overwrites native function
    • no sparse array support
    • no array-like-object or generic-this support
  6. Array#reverse

    • overwrites native function
    • no array-like-object or generic-this support support when custom inline argument is passed
  7. Array#some

    • overwrites native function
    • no array-like-object or generic-this support
  8. Function#bind

    • overwrites native function
    • no support for bound functions called as constructors
  9. String#strip (like String#trim)

    • no generic-this support

Sproutcore v1.6.0

  1. Array#forEach

    • no sparse array support
  2. Array#every

    • no sparse array support
  3. Array#filter

    • no sparse array support
  4. Array#indexOf

    • no sparse array support
    • incorrect negative fromIndex support
  5. Array#lastIndexOf

    • no sparse array support
    • incorrect fromIndex greater than length - 1 support
  6. Array#map

    • no sparse array support
  7. Array#reduce

    • no sparse array support
    • incorrect explicitly passed initialValue as undefined support
  8. SC.Array#slice

    • incorrect beginIndex greater than length support
    • incorrect negative beginIndex and endIndex support
  9. Array#some

    • no sparse array support

Underscore v1.1.7

  1. _.bind(func)

    • no support for bound functions called as constructors
  2. _.each(obj)

    • no generic-this support for obj
  3. _.every(obj)

    • no generic-this support for obj
  4. _.filter(obj)

    • no generic-this support for obj
  5. _.indexOf(obj)

    • no sparse array support
  6. _.lastIndexOf(obj)

    • no sparse array support
  7. _.map(obj)

    • no generic-this support for obj
  8. _.reduce(obj)

    • no generic-this support for obj
    • no support for explicitly passing an undefined value for initialValue (restricts internal native Array#reduce calls for consistency)
  9. _.reduceRight(obj)

    • no generic-this support for obj
    • no support for explicitly passing an undefined value for initialValue (restricts internal native Array#reduceRight calls for consistency)
  10. _.some(obj)

    • no generic-this support for obj

Valentine v1.1.9

I decided to include Valentine to show that libs new and old are affected by ES5 compliance issues.

  1. v.bind()

    • no support for bound functions called as constructors
  2. v.each(a)

    • no generic-this support for a
  3. v.every(a)

    • no array-like-object or generic-this support for a
  4. v.filter(a)

    • no array-like-object or generic-this support for a
  5. v.indexOf(a)

    • no array-like-object or generic-this support for a
    • no support for a negative fromIndex
  6. v.lastIndexOf(a)

    • no array-like-object or generic-this support for a
    • fromIndex incorrectly set to length instead of length - 1 when it's greater than length - 1
  7. v.map(a)

    • no generic-this support for a
  8. v.some(a)

    • no array-like-object or generic-this support for a
  9. v.trim(s)

    • no generic-this support for s

YUI v3.3.0

  1. Y.Array.each(a)

    • no sparse array support for a
    • no generic-this support for a
  2. Y.Array.indexOf(a)

    • no sparse array support for a
    • no generic-this support for a
  3. Y.Array.some(a)

    • no sparse array support for a
    • no generic-this support for a
  4. Y.Lang.trim(s)

    • no generic-this support for s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment