My shim was not to standards

Almost 8 months ago I showed a complete shim for Array.prototype.forEach. I thought it was pretty good and I’ve used it in a few projects, but there is one problem: I got it wrong. Not hugely wrong, but wrong enough. Like I said last time: a shim should only ever be to standards.

As well as an appology, this post will show what I got wrong and put it all right. At the end I’ll show a correct shim.

ToObject

This bug was harder to spot because I covered the missing bits in the main function. According to the specs, ToObject is supposed to throw the TypeError if the argument is null or undefined. Although this one isn’t a huge problem, it’s better to properly understand what the ToObject process is doing. Here’s a complete example:

function toObject(o) {
    if (o === undefined || o === null) {
        throw new TypeError(o + ' is null or not an object');
    }
    if ('a'[0] !== 'a'
            && Object.prototype.toString.call(o) === '[object String]') {
        o = o.split('');
    }
    return Object(o);
}

ToUint32

To my shame, I even spelled this one wrong. I spelt it U-N-I-T when it’s supposed to be U-I-N-T, short for unsigned integer.

Looking at the old function, I’m not even sure where I got “toUnit32″ from, it’s not very similar to the ES5 specification of ToUint32 at all. My one didn’t floor the number, didn’t multiply it by it’s sign, I didn’t even handle the negatives properly – too many differences! So, here’s a proper implimentation – with the correct spelling:

function sign(number) {
    return number < 0 ? -1 : 1;
}

function toUint32(n) {
    var number = +n,
        ret = 0,
        twoPow32 = Math.pow(2, 32);
    if (!isNaN(number) && isFinite(number)) {
        ret = (sign(number) * Math.floor(Math.abs(number))) % twoPow32;
        if (ret >= twoPow32 / 2) {
            ret -= twoPow32;
        }
    }
    return ret;
}

Skipping the empty entries of an Array

This one is a shocking lapse in judgement on my part. I misread the specification and other implimentations, so I was simply checking to see if the entry wasn’t undefined and skipping the entry if it was, but that’s not what the spec says. The spec says to skip the missing entries, not the undefined ones – and there’s a big difference. You see, given the following code, the function should run twice:

var myArray = ['a'];
myArray[3] = 'b';
// Now myArray is ['a', , , 'b'];
myArray.forEach(fn);

However in the following example, the function should run three times whereas my previous version only ran twice:

var myArray = ['a', undefined];
myArray[3] = 'b';
// Now myArray is ['a', undefined, , 'b'];
myArray.forEach(fn);

The key difference in my new implimentation is the use of Object.prototype.hasOwnProperty. This function checks to see if the key exists in the given object, as opposed to simply checking to see if the value is undefined. It’s possible to fill an Array with nothing but null and undefined, the modern methods should go through them all; they (and any shim) should only skip the missing entries.

My new shim

As far as I can tell from the specs, this is a correct shim of Array.prototype.forEach. I’ve left links to the functions in the source so please let me know if I’ve got anything wrong. This is the shim I’ll be using from now on:

(function () {

    'use strict';

    var undef,

        isStringArray = 'a'[0] === 'a',
        objProto = Object.prototype,
        toString = objProto.toString;

// http://es5.github.com/#x9.9
    function toObject(o) {
        if (o === undef || o === null) {
            throw new TypeError(o + ' is null or not an object');
        }
        if (!isStringArray && toString.call(o) === '[object String]') {
            o = o.split('');
        }
        return Object(o);
    }

// http://es5.github.com/#sign
    function sign(number) {
        return number < 0 ? -1 : 1;
    }

// http://es5.github.com/#x9.5
    function toUint32(n) {
        var number = +n,
            ret = 0,
            twoPow32 = Math.pow(2, 32);
        if (!isNaN(number) && isFinite(number)) {
            ret = (sign(number) * Math.floor(Math.abs(number))) % twoPow32;
            if (ret >= twoPow32 / 2) {
                ret -= twoPow32;
            }
        }
        return ret;
    }

// http://es5.github.com/#x15.4.4.18
    if (!Array.prototype.hasOwnProperty('forEach')) {
        Array.prototype.forEach = function (func, thisArg) {
            var index  = 0,
                array  = toObject(this),
                length = toUint32(array.length);
            if (toString.call(func) !== '[object Function]') {
                throw new TypeError(func + ' is not a function');
            }
            while (index < length) {
                if (objProto.hasOwnProperty.call(array, index)) {
                    func.call(thisArg, array[index], index, array);
                }
                index += 1;
            }
        };
    }

}());

One thought on “My shim was not to standards

  1. A suggestion — put all the function calls in the var declaration at the top so you can then move the if statement for !Array.prototype.hasOwnProperty(‘forEach’) above all, and halt the evaluation of the entire block if it’s unnecessary to parse.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>