///Enumerating JavaScript Objects

Enumerating JavaScript Objects

Enumerating JavaScript Objects

Enumeration lies at the heart of DOM Scripting:

var lists = document.getElementsByTagName("UL");
for (var i = 0; i < lists.length; i++) {
    lists[i].className = "menu";
}


for (var i = 0; i < array.length; i++) {
    print(array[i]);
}


for (var key in object) {
    print(object[key]);
}

Us JavaScripters are forever writing loops like these. To ease the strain on our keyboards Mozilla recently introduced a handy forEach method for arrays:

array.forEach(print);

If you don’t understand the code above then go and read the documentation.

That’s fine for arrays but what about DOM node lists? That’s where most of our loop writing is concentrated. Fortunately, the clever Mozilla developers provide generic array methods to help us:

var lists = document.getElementsByTagName("UL");
Array.forEach(lists, function(list) {
    list.className = "menu";
});

Cool huh? The Array.forEach method treats any object passed to it as if it were an array. So long as that object has a length property then we are OK.

We can easily implement this method for non-Mozilla browsers:

// array-like enumeration
if (!Array.forEach) { // mozilla already supports this
    Array.forEach = function(object, block, context) {
        for (var i = 0; i < object.length; i++) {
            block.call(context, object[i], i, object);
        }
    };
}

I’ve been using this technique for enumeration quite a lot recently and I decided to extend the idea:

// generic enumeration
Function.prototype.forEach = function(object, block, context) {
    for (var key in object) {
        if (typeof this.prototype[key] == "undefined") {
            block.call(context, object[key], key, object);
        }
    }
};


// globally resolve forEach enumeration
var forEach = function(object, block, context) {
    if (object) {
        var resolve = Object; // default
        if (object instanceof Function) {
            // functions have a "length" property
            resolve = Function;
        } else if (object.forEach instanceof Function) {
            // the object implements a custom forEach method so use that
            object.forEach(block, context);
            return;
        } else if (typeof object.length == "number") {
            // the object is array-like
            resolve = Array;
        }
        resolve.forEach(object, block, context);
    }
};

This allows me to write loops without knowing what kind of object I’m dealing with:

function printAll() {
    forEach (arguments, function(object) {
        forEach (object, print);
    });
};
// or
forEach (document.links, function(link) {
    link.className = "super-link";
});
// or
forEach ([1, 2, 3], print);
forEach ({a: 1, b: 2, c: 3}}, print);
// etc

Explanation

The global forEach function allows us to enumerate any object according to it’s type. If the object is array-like (has a length property) then we enumerate it like an array. All other objects are enumerated using the standard for var x in y mechanism.

When enumerating over objects, the discovered keys are compared against Object.prototype. If the key is defined on the Object object then it is not enumerated. That means that you cannot enumerate the built-in methods like toString and valueOf.

The global forEach function will delegate the enumeration of functions to Function.forEach. So, if you choose to enumerate over a Function object you will skip the built-in methods there too.

The Kick-Ass Bit

Although I’ve defined a forEach method on Function.prototype this is never called by the global forEach function (except when you are enumerating functions).

I’ve provided this as a bonus feature. :-) Basically, by calling the forEach method on a function you can enumerate an object and compare the keys with that function’s prototype. That means that you will only enumerate custom properties of the object. An example is required:

// create a class
function Person(name, age) {
    this.name = name || "";
    this.age = age || 0;
};
Person.prototype = new Person;


// instantiate the class
var fred = new Person("Fred", 38);


// add some custom properties
fred.language = "English";
fred.wife = "Wilma";

Enumerate using the standard forEach method:

forEach (fred, print);
// => name: Fred
// => age: 38
// => language: English
// => wife: Wilma

Enumerate using the Person.forEach method:

Person.forEach (fred, print);
// => language: English
// => wife: Wilma

Note that the properties defined on the prototype are not enumerated in the second example.

Conclusion

I’m totally in love with this technique. I now use it all the time. It makes my code more readable and I feel safer enumerating over objects on different platforms.

It also saves on typing. 

Download: forEach.js

2010-05-25T22:57:38+00:00 August 22nd, 2006|JavaScript|0 Comments

About the Author:

Leave A Comment