with Imagination: by Dustin Diaz

./with Imagination

A JavaScript, CSS, XHTML web log focusing on usability and accessibility by Dustin Diaz

Sugar Arrays: Porting over JavaScript 1.6 Array methods

Thursday, November 2nd, 2006

As of Firefox 1.5, there has been a new wide array of Array helpers that were included in JavaScript 1.6. That’s all fine and great, however there isn’t a single other browser on the market (as of this writing) that supports any of the new array methods (let alone JavaScript 1.6). What’s a developer to do?

Add Sugar

If you’re developing for todays grade A browsers, then you can count on being able to extend Array through it’s prototype. But, since I have a natural love toward things that are sweet, I’ve already done it for you with sugar and spice.

See the complete test specification document and download sources.

I used the Crock’s simple style of augmenting objects as a base to set up all my Array methods:

Class Augmentation

Function.prototype.method = function (name, fn) {
    this.prototype[name] = fn;
    return this;
};

The source includes each and all array methods that were added in JavaScript 1.6:

  • every
  • filter
  • forEach
  • map
  • some
  • indexOf
  • lastIndexOf

Here is an example of forEach method pulled from the source file:

Array.prototype.forEach

Array.
    method(
        'forEach',
        function(fn, thisObj) {
            var scope = thisObj || window;
            for ( var i=0; i < this.length; ++i ) {
                fn.call(scope, this[i], i, this);
            }
        }
);

Self-Defense

As a preventative measure to safeguard against overriding existing native browser implementations (wow that was a mouthful), I added one conditional statement at the beginning of the source that checks if Array.prototype.forEach has already been defined. See below for an illustration:

conditional object check

if ( !Array.prototype.forEach ) {
  // add sugar
}

The reason that I did not put a check for every other method in the implementation is that I figure if any browser hasn’t added forEach, they most likely didn’t add the rest. Likewise, if a browser manufacturer did implement forEach, I’m willing to put my money that they threw in the others as well.

Enjoy!

30 Responses to “Sugar Arrays: Porting over JavaScript 1.6 Array methods”

  1. Bramus!

    Reminds a bit of all the prototyping back in the days when IE6 got out (and thus backporting functions to the 5.x series).

    Dustin for President! Very nice work!

  2. Dean Edwards

    Erik Arvidsson already did this perfectly well:

    http://erik.eae.net/archives/2005/06/05/17.53.19/

  3. kentaromiura

    ..and so Andrea Giammarchi:
    http://www.devpro.it/JSL/

  4. Sugar Arrays: Porting over JavaScript 1.6 Array methods « [REF]

    [...] Link [...]

  5. Dustin Diaz

    Dean and Kent,
    Glad to hear it. I was searching around and couldn’t find anyone who had. At least it didn’t take too long. It made for an interesting 20 minute exercise. Erik and Andrea, good for both of you guys :) - (since I know the both of you might be headed toward this way soon enough).

    Now if only Node.prototype can inherit from Array.prototype

  6. Carson

    Excellent Dustin, thanks. I had no idea those others were out there either. This is educational as well. Thanks again for contributing to the community. It’s *greatly* appreciated.

  7. Lance Fisher

    Sweet!

  8. Peter Foti

    When’s the next screencast?

    Those first 3 seemed to come out within 2 weeks of each other, but now it’s been almost 2 months. This post would have made for a good screencast.

  9. Nicholas C. Zakas

    And another: http://www.nczonline.net/archive/2005/7/231

  10. Doug Clinton

    Nice. Couldn’t you build the self-defence into the “augmenting objects” method? The code would look like this:

    
    Function.prototype.method = function (name, fn) {
        if (!this.protoype[name]) {
            this.prototype[name] = fn;
        }
        return this;
    }
    
  11. Dustin Diaz

    Doug, I suppose that wouldn’t hurt, although I don’t think I’ve ran into a problem personally of overriding existing prototypes.

  12. Tommy Maintz

    Can someone tell me what the advantage is of adding methods to the prototype in Crock’s way instead of just adding them like Array.prototype.forEach = function(….); ?

  13. Dustin Diaz

    Hi Tommy,
    I believe I deleted one of your comments from ealier without telling you. Mainly because you had posted code that was garbled by Wordpress (probably because your forgot to encode the html).

    More importantly, to answer your question: They both do exactly the same thing. So really, it’s not that one is really better than the other, but with sugar you get the elegance of a classical OO approach, but with JavaScript.

    Also by returning this in the method method, you benefit from the cascade effect which allows you to continually add methods, one after another. Eg:

    var Person = function() {  };
    Person.
        method(
            'setName',
            function(name) {
                this.name = name;
            }
    ).
        method(
            'setAge',
            function(age) {
                this.age = age;
            }
    );
  14. Doug Clinton

    Dustin,

    The point is that you then wouldn’t need to add the check code you describe in your Self-Defense section and the question of “why are you only checking forEach?” would go away because each additional method would be automatically checked (though I think you are correct in your argument that no vendor is likely to add forEach without the others or vice-versa).

    Your sugar-adding code can then just consist of the unconditional chained calls to “method()”.

  15. Tommy Maintz

    Dustin,

    Tnx for your explanation. Sounds convincing :)

  16. Jim

    Dustin, you are always ahead of the game–well, ahead of me anyways. Cool stuff man!

    Jim

  17. Si Lloyd

    Nicely done, however your indexOf implementation doesn’t seem to make use of the start parameter, should this not be used to initiate i in the for loop? Or am I missing something ?

  18. Dustin Diaz

    Lloyd, I’m not sure what you mean? This isn’t the same indexOf for strings, and I’ve tested this implementation on various test cases. It works just fine. Noticing that other implementations are written almost exactly the same confirms that it’s correct as well.

  19. Si Lloyd

    Yeah it works, but the Mozilla array.indexOf specs shows two parameters, the search element, obviously, and the fromIndex to start from, which you have half implemented.

    Your code:

    
    Array.prototype.indexOf = function(el, start) {
    var start = start || 0;
    for ( var i=0; i < this.length; ++i ) {
    ...
    

    Should it not be:

    
    Array.prototype.indexOf = function(el, start) {
    var start = start || 0;
    for ( var i=start; i < this.length; ++i ) {
    ...
    

    ?

  20. Dustin Diaz

    Ah. I see now. Good catch. Thanks for spotting that.

  21. Links comentados via del.icio.us - 7 » Japs

    [...] A quarta parte de hoje são alguns links sobre desenvolvimento: Controle de sessão e PHP, Modelo de orientação a objetos em PHP 5, Minikit: visual effect bag, Sugar Arrays: Porting over JavaScript 1.6 Array methods, Remote Scripting Transport Patent, Amberjack: JavaScript Site Tour Creator, 3D Rendering in JavaScript e 10 things you (probably) didn’t know about PHP. [...]

  22. Cristian

    Thanks
    Keep up the good work :)

  23. Stefan Van Reeth

    Hi to all,

    I just sent Dustin a lengthy mail about the prototypes, since I somewhere got the idea I didn’t find the reply box. Doh. That will teach me to surf with 30 tabs open lol.

    I had a remark about indexOf that Si Lloyd already spotted, and a few other things were addressed. Most important part was the looping through the array indexes as done by forEach, every, some, filter and map.

    What’s wrong with it? Nothing. But I like to code so my users can’t hurt themselves. So I like to build some sanity checking into everything. In this case, there is a flaw in the implementation that only creeps up when the programmer does stupid things.

    Since even programmers can be stupid, I’d rather prevent ‘em from doing it. Now to the point. Suppose one does this:

    
    var test = new Array();
    test[0] = "someValue";
    test[1000000000] = "otherValue";
    test.forEach(someFunc);
    

    Why one would do this, is beyond the point. In this case, Dustin’s implementations run through all the indexes from 0 to 1000000000, while only performing two actions. Not very efficient, and on my pc it took sometimes ages to finish.

    My proposal is to loop through the Array object members. I admit, in the case of small arrays, this would be less efficient. But I couldn’t record any speed penalties, even not when doing the whole thing a few thousand times over again. And in the case above, my versions loops only the prototype members and the two indexes.

    How I implemented it? Like this:

    
    for (var index in this)
    {
        if (isNaN(parseInt(index)))
        {
            continue;
        }
        else
        {
            pass = new Array(this[index], index, this);
            name.apply(scope, pass);
        }
    }
    

    First of all, my versions is intended to work on any js implementation from version 1.2 and up. That means also outside browsers. So that’s why i use apply() instead of call(). The part with continue is not needed at the moment, but is a placeholder for debugging/logging code. Oh yeah, I called the function to be called name and declared pass as a variable somewhere before. So maybe this version would be better for Dustin’s audience:

    
    for (var index in this)
    {
        if (!isNaN(parseInt(index)))
        {
            fn.call(scope, this[index], index, this);
        }
    }
    

    Oh and please, please, please: no remarks about coding style. That’s each to his own I think. And after all, when my version gets compressed, it hardly is bigger than Dustin’s one (also compressed with same app). I just like to have more whitespace in my developping version for readability.

    I also had another remark about not following the full specs in indexOf and lastIndexOf, but it’s a minor issue since almost nobody uses will those like that. I’m talking about using negative offsets. On the other hand, that’s also true for all the above ;).

    Aaaaaaaaanyway, this has gotten already too lengthy. If someone wants to know, I post it here later.

  24. Stefan Van Reeth

    Ooops, typo =>

    I also had another remark about not following the full specs in indexOf and lastIndexOf, but it’s a minor issue since almost nobody will use them like that. I’m talking about using negative offsets. On the other hand, that’s also true for all the above ;).

  25. zhanghao

    oh,this looks something wrong.the code isn’t entirely.pls delete the up comment.

  26. JavaScript Chaining

    [...] To briefly explain what’s going on, you’ll see that I’m using two of the JavaScript 1.6 Array extras called filter and map which allow me to essentially weed out the non-checked checkboxes, and then map those elements with their values. For cross-browser compatibility I used my own sugar arrays. [...]

  27. JFSIII

    Am I the only one who finds

    
    ['dustin', 'robert', 'virus', 'vince'].filter(
    	function(el, i, ar) {
            if ( el !== 'virus' ) {
                return el;
            }
        }
    );
    

    less than sugary sweet?

    A function inside my “shortcut”? That’s behaving more like

    forEach()

    or

    map()

    in my opinion.

    I took a few minutes to change the methods so they could be called as I would expect.

    
    // every()
    Array.prototype.every = function(compare, thisObj) {
        for ( var i=0, j=this.length; i < j;   i ) {
            if (this[i] !== compare) {
                return false;
            }
        }
        return true;
    };
    
    // Tests for every()
    ['foo', 'foo', 'foo'].every('foo');  //true
    ['foo', 'foo', 'foo'].every('bar');  //false
    ['dustin', 'robert', 'virus', 'vince'].every('dustin'); //false
    
    // some()
    Array.prototype.some = function(compare, thisObj) {
        for ( var i=0, j=this.length; i < j;   i ) {
            if (this[i] === compare) {
                return true;
            }
        }
        return false;
    };
    
    // Tests for some()
    ['foo', 'foo', 'foo'].some('foo');  //true
    ['foo', 'foo', 'foo'].some('bar');  //false
    ['dustin', 'robert', 'virus', 'vince'].some('virus');  //true
    
    // filter()
    Array.prototype.filter = function(filter, thisObj) {
        var a = [];
        for ( var i=0, j=this.length; i < j;   i ) {
            if (this[i] !== filter){
                a.push(this[i]);
            }
        }
        return a;
    };
    
    // Tests for filter()
    ['dustin','robert', 'virus', 'vince'].filter('virus');         // ['dustin', 'robert', 'vince']
    ['dustin', '', '','robert', ,'virus', 'vince'].filter('');     // ['dustin', 'robert', undefined, 'virus', 'vince']
    ['dustin', , ,'robert', undefined,'virus', 'vince'].filter();  // ['dustin', 'robert', 'virus', 'vince']
    
    
    ['dustin','robert', 'virus', 'vince'].filter('virus')

    is sweeter than

    ['dustin', 'robert', 'virus', 'vince'].filter(
    	function(el, i, ar) {
            if ( el !== 'virus' ) {
                return el;
            }
        }
    )

    no?

  28. James Aylett

    @JFSIII: your filter() is doing something different to Dustin’s. What you could do, however, which would be kind of neat, is have a filter that would work both ways, based on whether the formal parameter was a Function object or not. filter() should in general take a callable though - what if you wanted to filter an array of numbers to get rid of values outside the range 20-45, say?

  29. Levi Hackwith

    I fail to see the main difference between map() and foreach(). It appears to me that they could both be used to accomplish the same purpose.

  30. JFSIII

    @James,

    Will you (or anyone) please point out the differences in behavior (results) between version of filter I posted and the one from Sugar Arrays?

    I do like the idea a function as a parameter, though.

Get "JavaScript Design Patterns"

"As a web developer, you'll already know that JavaScript™ is a powerful language, allowing you to add an impressive array of dynamic functionality to otherwise static web sites. But there is more power waiting to be unlocked--JavaScript is capable of full object-oriented capabilities, and by applying OOP principles, best practices, and design patterns to your code, you can make it more powerful, more efficient, and easier to work with alone or as part of a team."

Buy JS Design Patterns from Amazon.com Buy JS Design Patterns from Apress

All content copyright © 2003 - 2009 under the Creative Commons License.

Archives | Blog Search