138

As detailed elsewhere, and otherwise apparently well-known, Internet Explorer (definitely version 7, and in some instances, version 8) do not implement key functions, in particular on Array (such as forEach, indexOf, etc).

There are a number of workarounds here and there, but I'd like to fold a proper, canonical set of implementations into our site rather than copy and paste or hack away at our own implementations. I've found js-methods, which looks promising, but thought I'd post here to see whether another library comes more highly-recommended. A couple of miscellaneous criteria:

  • The library should just be a no-operation for those functions that a browser already has implementations for (js-methods appears to do quite well here).
  • Non-GPL, please, though LGPL is acceptable.
Community
  • 1
  • 1
cemerick
  • 5,916
  • 5
  • 30
  • 51

6 Answers6

221

Many use the MDC fallback implementations (eg. for indexOf). They're generally rigorously standards-compliant, even to the extent of explicitly checking the types of all the arguments.

Unfortunately whilst it is clear that the authors regard this code as trivial and freely-usable, there doesn't seem to be an explicit licence-grant to put this in writing. The wiki as a whole is CC Attribution-ShareAlike, if that's an acceptable licence (though CC isn't designed for code as such).

js-methods looks OK in general, but is not as standards-compliant around the edges of how the functions are supposed to be (eg. undefined list items, functions that mutate the list). It's also full of other random non-standard methods, including some questionable ones like the dodgy stripTags and the incomplete UTF-8 codec (which is also a bit unnecessary given the unescape(encodeURIComponent) trick).

For what it's worth, here's what I use (which I hereby release into the public domain, if it can be said to be copyrightable at all). It's a bit shorter than the MDC versions as it doesn't attempt to type-sniff that you haven't done something silly like pass non-function callbacks or non-integer indexes, but apart from that it attempts to be standards-compliant. (Let me know if I've missed anything. ;-))

'use strict';

// Add ECMA262-5 method binding if not supported natively
//
if (!('bind' in Function.prototype)) {
    Function.prototype.bind= function(owner) {
        var that= this;
        if (arguments.length<=1) {
            return function() {
                return that.apply(owner, arguments);
            };
        } else {
            var args= Array.prototype.slice.call(arguments, 1);
            return function() {
                return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
            };
        }
    };
}

// Add ECMA262-5 string trim if not supported natively
//
if (!('trim' in String.prototype)) {
    String.prototype.trim= function() {
        return this.replace(/^\s+/, '').replace(/\s+$/, '');
    };
}

// Add ECMA262-5 Array methods if not supported natively
//
if (!('indexOf' in Array.prototype)) {
    Array.prototype.indexOf= function(find, i /*opt*/) {
        if (i===undefined) i= 0;
        if (i<0) i+= this.length;
        if (i<0) i= 0;
        for (var n= this.length; i<n; i++)
            if (i in this && this[i]===find)
                return i;
        return -1;
    };
}
if (!('lastIndexOf' in Array.prototype)) {
    Array.prototype.lastIndexOf= function(find, i /*opt*/) {
        if (i===undefined) i= this.length-1;
        if (i<0) i+= this.length;
        if (i>this.length-1) i= this.length-1;
        for (i++; i-->0;) /* i++ because from-argument is sadly inclusive */
            if (i in this && this[i]===find)
                return i;
        return -1;
    };
}
if (!('forEach' in Array.prototype)) {
    Array.prototype.forEach= function(action, that /*opt*/) {
        for (var i= 0, n= this.length; i<n; i++)
            if (i in this)
                action.call(that, this[i], i, this);
    };
}
if (!('map' in Array.prototype)) {
    Array.prototype.map= function(mapper, that /*opt*/) {
        var other= new Array(this.length);
        for (var i= 0, n= this.length; i<n; i++)
            if (i in this)
                other[i]= mapper.call(that, this[i], i, this);
        return other;
    };
}
if (!('filter' in Array.prototype)) {
    Array.prototype.filter= function(filter, that /*opt*/) {
        var other= [], v;
        for (var i=0, n= this.length; i<n; i++)
            if (i in this && filter.call(that, v= this[i], i, this))
                other.push(v);
        return other;
    };
}
if (!('every' in Array.prototype)) {
    Array.prototype.every= function(tester, that /*opt*/) {
        for (var i= 0, n= this.length; i<n; i++)
            if (i in this && !tester.call(that, this[i], i, this))
                return false;
        return true;
    };
}
if (!('some' in Array.prototype)) {
    Array.prototype.some= function(tester, that /*opt*/) {
        for (var i= 0, n= this.length; i<n; i++)
            if (i in this && tester.call(that, this[i], i, this))
                return true;
        return false;
    };
}

Other ECMA262-5 methods not implemented here include Array reduce/reduceRight, the JSON ones and the few new Object methods that can be reliably implemented as JS functions.

bobince
  • 528,062
  • 107
  • 651
  • 834
  • 5
    Thanks for that pointer -- the other links I've seen into mozdev where such impls might be found were stale. FYI, the code is MIT-licensed, as specified here: https://developer.mozilla.org/Project:Copyrights (about as good as you can get! :-) – cemerick May 08 '10 at 02:23
  • 1
    Interestingly, if I reference a js file containing all of the MDC ECMA262-5 impls before jquery 1.4.2, jquery is broken -- e.g. all selectors fail, returning null. Moving the MDC impls after jquery leads to expected behaviour. Very odd. – cemerick May 09 '10 at 14:42
  • That *is* curious! Will look at that (do you have a test case?)... I can't immediately think why this might happen, though what jQuery does on line 72 looks suspicious. – bobince May 10 '10 at 01:33
  • It looks like a browser cache flush fixed things up. I can't reproduce that under any circumstances now. :-) – cemerick May 11 '10 at 14:31
  • 4
    NOTE: in most browsers where these stubs are needed, if you do a "for (index in somearray) {...}" you will need to use somearray.hasOwnProperty(index) as a check. IE<=8's JS engine will include the array.prototype extensions in this. Google Adwords async code doesn't do this. Best to use Underscore, or another library's functionality that standardizes on this. – Tracker1 Jan 30 '12 at 17:49
  • @bobince: I used indexOf code from your solution but when I write alert(subItem[1].indexOf(",")); where subItem[1]=CSMTestPWXListinerService,CSMTestPWXListinerService_ManualyAdded" it returns -1` i.e., always it returns -1. What is wrong? – AabinGunz Mar 19 '12 at 11:13
  • @Abishek: What type is `subItem[1]`? It looks like you're doing a `String#indexOf`, which remains unaffected by `Array#indexOf` or anything else here. – bobince Mar 19 '12 at 23:38
  • Note that `undefined` isn't actually a keyword. You should use `typeof i === 'undefined'`. – Azmisov Jul 09 '13 at 21:00
  • @Azmisov: neither are `Function`, `Array`, `String` or any of the builtin objects' prototype properties. You can't ever be fully proof against sabotage in JS; I choose not to ugly it up by trying. – bobince Jul 09 '13 at 21:10
  • 1
    This is the fastest indexOf() implementation for IE 8 I could find. Thanks! – Alex Denysenko May 19 '15 at 15:23
  • I observed that affecting Array.prototype may cause some exception in IE. I recommend using Object.defineProperty as in http://stackoverflow.com/a/35135200/3057341 . – Psddp Sep 29 '16 at 16:32
27

Take a look at Underscore.js.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
rfunduk
  • 30,053
  • 5
  • 59
  • 54
  • 2
    ES5Shim and other stubs (like from MDC) tend to have other consequences as well. It's best to use underscore or another library for these types of functions, which will use the internal methods where available. – Tracker1 Jan 30 '12 at 17:51
  • With the Underscore.js var arr=['a','a1','b'] _.filter(arr, function(a){ return a.indexOf('a') > -1; }) – srikanth_yarram Dec 10 '14 at 11:30
9

Kris Kowal has compiled a small library that acts as a shim for ECMAScript 5 functions that may be missing from the browser's implementation. Some of the functions have been revised numerous times by other people to be optimized for speed and to work around browser bugs. The functions are written to follow the specification as closely as possible.

es5-shim.js was released under the MIT license, the Array.prototype extensions are near the top and you can chop and remove any functions you don't need quite easily. I also suggest you minify the script as the comments make it much larger than it needs to be.

Community
  • 1
  • 1
Andy E
  • 338,112
  • 86
  • 474
  • 445
1

Those scripts don't work well in my tests. I create a file with the same functions based on MDN documents.

Too many problems areas are solved in Internet Explorer 8. See the code in egermano / ie-fix.js.

egermano
  • 1,809
  • 1
  • 12
  • 13
1

By 'not implement key functions' you actually means 'conforms to the ECMA 262 3'rd ed' right? :)

The methods you are referring to are part of the new 5'th edition - for browsers not supporting this you can use the following 'shim' that extends 3'rd into 5'th http://github.com/kriskowal/narwhal-lib/blob/narwhal-lib/lib/global-es5.js.

Sean Kinsey
  • 37,689
  • 7
  • 52
  • 71
  • 1
    That's a good start, but there are quite a few errors in the implementations not taken from MDC. eg. many of the array methods don't pass enough arguments to their callbacks, and don't act quite right in the case of array mutation in the callback function. – bobince May 07 '10 at 18:56
  • I'll take whatever I can get to make js a more sane / minimally-capable language. :-) – cemerick May 11 '10 at 14:32
0

With the Underscore.js

var arr=['a','a1','b'] _.filter(arr, function(a){ return a.indexOf('a') > -1; })

srikanth_yarram
  • 957
  • 9
  • 16