MichD

 

JavaScript forEach Object, Array, and String

in Blog

In many programming languages, you have a simple for each construct available to quickly go over the contents of just about anything iterable. In JavaScript it isn’t so straightforward. To rectify this, I’ve written up a function that provides a single interface to iterate over the elements of an array, the values in an object, and “even” the characters in a string.

It works with a callback function that takes index/key and value as parameters. This callback function is executed for every item in the collection passed.

The benefit of using this is that you have very readable code; most programmers will intuitively know how it works because of the familiar collection, index and value interface.

To the code!

(function () {
    "use strict";

    /**
     * Iterate over an Object, Array of String with a given callBack function
     *
     * @param {Object|Array|String} collection
     * @param {Function} callBack
     * @return {Null}
     */
    function forEach(collection, callBack) {
        var i = 0, // Array and string iteration
            iMax = 0, // Collection length storage for loop initialisation
            key = '', // Object iteration
            collectionType = '';

        // Verify that callBack is a function
        if (typeof callBack !== "function") {
            throw new TypeError("forEach: callBack should be function, " +
              typeof callBack + " given.");
        }

        // Find out whether collection is array, string or object
        switch (Object.prototype.toString.call(collection)) {
          case "[object Array]":
              collectionType = "array";
              break;

          case "[object Object]":
              collectionType = "object";
              break;

          case "[object String]":
              collectionType = "string";
              break;

          default:
              collectionType = Object.prototype.toString.call(collection);
              throw new TypeError(
                "forEach: collection should be array, object or string, " +
                collectionType + " given.");
        }

        switch (collectionType) {
          case "array":
              for (i = 0, iMax = collection.length; i < iMax; i += 1) {
                  callBack(collection[i], i);
              }

              break;

          case "string":
              for (i = 0, iMax = collection.length; i < iMax; i += 1) {
                  callBack(collection.charAt(i), i);
              }

              break;

          case "object":
              for (key in collection) {
                  // Omit prototype chain properties and methods
                  if (collection.hasOwnProperty(key)) {
                      callBack(collection[key], key);
                  }
              }

              break;

          default:
              throw new Error(
              "Continuity error in forEach, this should not be possible.");
              break;
        }

        return null;
    }

    //Example uses

    // Array example
    forEach(["a", "b", "c"], function (value, index) {
        console.log(index + ": " + value);
    });

    // Object example
    forEach({"foo": "bar", "barfoo": "foobar"}, function (value, key) {
        console.log(key + ": " + value);
    });

    // String example
    forEach("Hello, world!", function (character, index) {
        console.log(index + ": " + character);
    });
}());

Preparation and parameter checking

We start, as we should, by declaring the local variables. i, iMax and key are used in the iteration later on, collectionType stores what we’re dealing with.

Validating the callBack parameter

We check whether callBack is an actual function, as this whole function is useless if nothing can be done while iterating.

Validating the collection

The first switch construct determines what type of collection we are dealing with. Normally you would use typeof collection here, but JavaScript reports ‘object’ when collection is an array (because arrays are objects). As a result, we have no way to differentiate between objects and arrays through typeof.

To get around this, we use toString, from the Object prototype. We use Object’s toString method specifically, because it’s only one that actually gives us the information we need; if we were to use collection.toString(), we’d get “[object Object]” for an object, “a,b,c” for an array, and “hello world” for a string—not very helpful.

Object’s toString yields “[object Object]“, “[object Array]” and “[object String]” for the aforementioned examples; definitely useful for what we’re trying to achieve. To call Object’s toString method on non-Object variables, we use Object.prototype.toString.call(collection)

If through all that it turns out collection is neither Object, Array or String, an error is thrown, and the function execution terminated.

The iteration

Based on collectionType we just determined, we now iterate over collection. Again we use the readable switch statement. Array and String

Arrays and strings behave much the same as far as iterating them goes. As you access an element at index i in an array using myArray[i], you access the character at index i in a string through myString[i]. Because of this, we can use the exact same for loop to iterate an array and a string, hence the fall-through in the switch construct.

EDIT: People on IRC and Reddit have pointed out that string[index] is not supported below IE7 or IE8, so I’ve updated the snippet to use string.charAt(index) instead, and removed the switch fall-through.

For every item or character in that array or string, callBack is executed with the index and value paramaters.

A note on for loops

There is a tiny note-worthy bit in the for loop code. I believe most people would initialise their for loops like this (at least I’ve done it this way for years):

var i = 0;

for (i = 0; i < collection.length; i += 1) {
    // ... code ...
}

There is something sub-optimal about that approach; every time the condition (i &lt; collection.length) is checked—before every iteration, the length property of collection is accessed. This can be a performance bottleneck, as the length needs to be recounted every time, since it is possible that it has changed since the last iteration.

Instead, store the length in another variable on initialisation, then just compare to the variable in the condition, like this:

var i,
    iMax = 0;

for (i = 0, iMax = collection.length; i < iMax; i += 1) {
    // ... code ...
}

Objects

Iterating over the key/value pairs in an object is another story. The for...in loop is available, but there is a little caveat to it. Objects inherit properties and methods from the prototype chain. Prototypes are a whole other story, but suffice to say that we don’t want to include those properties and methods in our iteration.

Luckily, there is a function in the Object prototype that lets us figure out whether a property or method is part of that object itself or part of its prototype chain. As you can see, all we have to set up is a for...in loop, then in that loop check whether the object owns the property by name key. If that is the case, callBack is called with key and value parameters.

Return

Since the function does all its magic through the callBack function, it does not need to return anything. Hence, we return null.

Examples

The code above includes a few examples. I’ll add some other examples to show what each forEach use is equivalent to using regular loops.

Array example

Using forEach:

var myArray = ["a", "b", "c"];

forEach(myArray, function (value, index) {
    console.log(key + ": " + value);
});

Output:

0: a
1: b
2: c

Using normal for loop:

var myArray = ["a", "b", "c"],
    i = 0,
    iMax = 0;

for (i = 0, iMax = myArray.length; i < iMax; i += 1) {
    console.log(i + ": " + myArray[i]);
}

Output:

0: a
1: b
2: c

Object example

Using forEach:

var myObject = {
    "Douglas": "Adams",
    "Robert": "Heinlein",
    "Isaac": "Asimov"
};

forEach(myObject, function (value, key) {
    console.log(key + ": " + value);
});

Output:

Douglas: Adams
Robert: Heinlein
Isaac: Asimov

Using for...in and hasOwnProperty:

var myObject = {
        "Douglas": "Adams",
        "Robert": "Heinlein",
        "Isaac": "Asimov"
    },
    key = '';

for (key in myObject) {
    if (myObject.hasOwnProperty(key)) {
        console.log(key + ": " + myObject[key]);
    }
}

Output:

Douglas: Adams
Robert: Heinlein
Isaac: Asimov

Beware of HTMLCollections

I’ve written this to be environment-agnostic (save for the console.log calls in examples). If you want to use this with results from document.getElementsByTagName(), it won’t work as it is, reason being that getElements… returns not an [object Object], array or string, instead it returns [object HTMLCollection], which is not catered for. You can adapt forEach to also work with HTMLCollections, or you can convert your HTMLCollections to arrays. I hope that saves some people headaches.

Update (same day)

People on IRC and Reddit have pointed out the standard value, index array order from the spec, so I’ve updated the code to use that order instead. They have also pointed out that < IE7/IE8 does not support string[index], so that’s been updated to use string.charAt(index) instead.