Iterating over arrays and objects in JavaScript

[2011-04-20] dev, javascript, jslang
(Ad, please don’t block)
This post explains three approaches for extracting information from arrays and objects:
  1. for loops,
  2. array methods (courtesy of ECMAScript 5 [1]),
  3. listing property keys.
It concludes with best practices for applying these approaches.

for loops

All for loops can be used with the following statements.
  • break [label]: exit from a loop.
  • continue [label]: stop the current loop iteration, immediately continue with the next one.
  • label: A label is an identifier followed by a colon. In front of a loop, a label allows you to break or continue that loop even from a loop nested inside of it. In front of a block, you can break out of that block. In both cases the name of the label becomes an argument of break or continue. Example for breaking out of a block:
        function findEvenNumber(arr) {
            loop: { // label
                for(var i=0; i<arr.length; i++) {
                    if ((arr[i] % 2) === 0) {
                        console.log("Found: "+arr[i]);
                        break loop;
                    }
                }
                console.log("No even number found.");
            }
            console.log("DONE");
        }
    

for

Syntax:
    for ([start]; [condition]; [final-expression])
        statement
Rules:
  • Traditional way of iterating over arrays.
  • Can use var, but scope is always the complete surrounding function.
Example:
    var arr = [ "a", "b", "c" ];
    for(var i=0; i < arr.length; i++) {
        console.log(arr[i]);
    }

for...in

Syntax
    for (variable in object)
        statement
Rules:
  • Iterate over property keys, including inherited ones.
  • Don’t use for arrays. It iterates over both array indices and property keys. There will thus be problems as soon as someone adds a property to an array.
  • Can use var, but scope is always the complete surrounding function.
  • Properties can be deleted during iteration.
Pitfall: Iterates over both array indices and property keys.
    > var arr = [ "a", "b", "c" ];
    > arr.foo = true;
    > for(var key in arr) { console.log(key); }
    0
    1
    2
    foo
Pitfall: Iterates over inherited properties.
    function Person(name) {
        this.name = name;
    }
    Person.prototype = {
        describe: function() {
            return "Name: "+this.name;
        }
    };
    var person = new Person("Jane");
    for(var key in person) {
        console.log(key);
    }
Output:
    name
    describe
Skip inherited properties: via hasOwnProperty().
    for(var key in person) {
        if (person.hasOwnProperty(key)) {
            console.log(key);
        }
    }

for each...in

Non-standard (Firefox only), iterates over the values of an object. Don’t use it.

Array methods for iteration

Iterate

Iterate over the elements in an array. The methods don’t have a result, but you can produce one in the callback as a side effect. They all have the following signature:
    function(callback, [thisValue])
Parameters:
  • The callback has the following signature (sometimes it returns no value, sometimes a boolean).
        function([element], [index], [collection])
    
  • The thisValue argument allows you to specify an object that is to be accessed via this in callback.
Iteration methods:
  • Array.prototype.forEach() is similar to for...in, but only iterates over an object’s own properties.
  • Array.prototype.every(): returns true if the callback returns true for every element.
  • Array.prototype.some(): returns true if the callback returns true for at least one element.
Example:
    var arr = [ "apple", "pear", "orange" ];
    arr.forEach(function(elem) {
        console.log(elem);
    });
Pitfall: forEach() does not support break. Use every() instead:
    function breakAtEmptyString(arr) {
        arr.every(function(elem) {
            if (elem.length === 0) {
                return false; // break
            }
            console.log(elem);
            return true; // don’t forget!
        });
    }
every() returns false if a break happened and true, otherwise. This allows you to react to the iteration finishing successfully (something that is slightly tricky with for loops). Caveat: You need to return a “true” value to keep going. If you want to avoid that, you can use some() and return true to break:
    function breakAtEmptyString(arr) {
        arr.some(function(elem) {
            if (elem.length === 0) {
                return true; // break
            }
            console.log(elem);
            // implicit: return undefined (interpreted as false)
        });
    }

Transform

Transformation methods take an input array and produce an output array, while the callback controls how the output is produced. The callback has the same signature as for iteration:
    function([element], [index], [collection])
Methods:
  • Array.prototype.map(callback, [thisValue]): Each output array element is the result of applying callback to an input element.
  • Array.prototype.filter(callback, [thisValue]): The output array contains only those input elements for which callback returns true.

Reduce

For reducing, the callback has a different signature:
    function(previousElement, currentElement, currentIndex, collection)
Methods:
  • Array.prototype.reduce(callback, [initialValue]): Compute a value by applying callback to pairs (previousElement, currentElement) of array elements.
  • Array.prototype.reduceRight(callback, [initialValue]): Same as reduce(), but from right to left.
Example:
    // Sum of all array elements:
    [17, 5, 4, 28].reduce(function(prev, cur) {
        return prev + cur;
    });

Listing property keys

  • Object.keys(obj): Lists all enumerable own property keys of an object. Example:
        > Object.keys({ first: "John", last: "Doe" })
        [ 'first', 'last' ]
    
  • Object.getOwnPropertyNames(): Lists all own property keys of an object, including non-enumerable ones.
        > Object.getOwnPropertyNames(Number.prototype)
        [ 'toExponential'
        , 'toString'
        , 'toLocaleString'
        , 'toPrecision'
        , 'valueOf'
        , 'toJSON'
        , 'constructor'
        , 'toFixed'
        ]
        > Object.keys(Number.prototype)
        []
    
    Comment: The main reason that prototype methods are not enumerable is to hide them from iteration mechanisms that include inherited properties.

Best practices

Iterating over arrays

Options:
  • Simple for loop.
  • One of the iteration methods.
  • Never use for...in or foreach...in.

Iterating over objects

Options:
  • Combine for...in with hasOwnProperty(), in the manner described above.
  • Combine Object.keys() or Object.getOwnPropertyNames() with forEach() array iteration.
        var obj = { first: "John", last: "Doe" };
        // Visit non-inherited enumerable keys
        Object.keys(obj).forEach(function(key) {
            console.log(key);
        });
    
Other tasks:
  • Iterate over the property (key,value) pairs of an object: Iterate over the keys, use each key to retrieve the corresponding value. Other languages make this simpler, but not JavaScript.

Related reading

  1. What’s new in ECMAScript 5