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? TheArray.forEach
method treats any object passed to it as if it were an array. So long as that object has alength
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 its 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.
0 comments: