Over a million developers have joined DZone.

Rethinking JavaScript Object Enumeration

· Web Dev Zone

Make the transition to Node.js if you are a Java, PHP, Rails or .NET developer with these resources to help jumpstart your Node.js knowledge plus pick up some development tips.  Brought to you in partnership with IBM.

In JavaScript, enumeration across regular (non-Array) Objects is often more painful than it should be. Arrays are merrily dispatched through for and while loops using all manner of crazy, fun techniques; Objects are forever at the mercy of the pedestrian, one directional for-in loop, without which we can’t even learn the names and length of its own property set. Arrays have access to a plethora of elegant higher order functions (forEach, map, filter etc.); Objects don’t. Until now, that is.

Borrowing from Prototype.js, ECMAScript 5 defines two nifty new methods Object.keys(obj) and the rather clunkily named Object.getOwnPropertyNames(obj). They already work in the current versions of Chrome and Safari and will be supported in Firefox 4 and IE9.

Object.keys(obj)

This method returns an array of all enumerable property names defined by a given object (inherited properties are not considered). Note that the sequence is based on the default for-in looping sequence which may vary slightly between browsers (for full details on for-in sequence see this article):

//Chrome, Safari, FF4, IE9
var purchases = {butter: 3.00, soap: 5.95, pineapple: 3.50 };

Object.keys(purchases); //['butter', 'soap', 'pineapple']

Now we can iterate an object’s properties in any sequence using a for loop…

//Chrome, Safari, FF4, IE9
var keys = Object.keys(purchases), totalCost = 0;

for (var i=keys.length; i--;) {
    totalCost += purchases[keys[i]];
}

totalCost; //12.45

…or a while loop…

//Chrome, Safari, FF4, IE9
var keys = Object.keys(purchases), i=keys.length, totalCost = 0;

while  (i--) {
    totalCost += purchases[keys[i]];
}

totalCost; //12.45

For those browsers that don’t yet implement Object.keys we can apply the following shim (thanks to @jdalton for reminding me to add type checking) :

//all browsers
if (typeof Object.keys != 'function') {
    Object.keys = function(obj) {
       if (typeof obj != "object" && typeof obj != "function" || obj == null) {
            throw TypeError("Object.keys called on non-object");
       }
       var keys = [];
       for (var p in obj) obj.hasOwnProperty(p) &&keys.push(p);
       return keys;
    }
}

Object.keys({a:1, b:2, c:3}); //['a', 'b', 'c']

Now its easy to use an Object with one of the higher order iterators supplied by Array.prototype…

var thing = {
    size: 14,
    color: 'kind of off-white',
    greet: function() {return "thronk"}
};

var thingFunctions = Object.keys(thing).filter(function(e) {
    return typeof thing[e] == 'function'
});

thingFunctions; //["greet"]

…and we can use the map function to create an Object.values method too (because you know Harmony will be adding it any minute now ;-) )

Object.values = function(obj) {
    return Object.keys(obj).map(function(e) {
        return obj[e]
    });
}

Object.values({a:1, b:2, c:3}); //[1, 2, 3]

 

Object.getOwnPropertyNames(obj)

This one is a gem. Its similar to Object.keys but additionally returns the names of non-enumerable properties (again, inherited properties are not included). Now, at long last, you can list the properties of Math! The following snippet collects every Math function that expects exactly one argument and invokes it, passing the number 10…

//Chrome, Safari, FF4, IE9

var mathProps = Object.getOwnPropertyNames(Math);
var oneArgMathFunctions = mathProps.forEach(function(e) {
    if((typeof Math[e] == 'function') && (Math[e].length == 1)) {
        console.log("Math." + e + "(10) -> " + Math[e](10));
    }
});
//Math.cos(10) -> -0.8390715290764524
//Math.log(10) -> 2.302585092994046
//Math.tan(10) -> 0.6483608274590867
//Math.sqrt(10) -> 3.1622776601683795
//etc...

…and here’s an array of all the properties of String.prototype…

//Chrome, Safari, FF4, IE9

Object.getOwnPropertyNames(String.prototype);
//["length", "constructor", "concat", "localeCompare", "substring", "italics", "charCodeAt", "strike", "indexOf", "toLowerCase", "trimRight", "toString", "toLocaleLowerCase", "replace", "toUpperCase", "fontsize", "trim", "split", "substr", "sub", "charAt", "blink", "lastIndexOf", "sup", "fontcolor", "valueOf", "link", "bold", "anchor", "trimLeft", "small", "search", "fixed", "big", "match", "toLocaleUpperCase", "slice"]

Unlike Object.keys we can’t replicate Object.getOwnPropertyNames using regular JavaScript since non-enumerable properties are out of bounds when using traditional iteration loops. Check out this log for an insight into the hazards encountered during the webkit implementation.

A word on TypeErrors

 
EcmaScript 5 is making gestures towards limiting auto-coercion, notably with the introduction of Strict Mode. That effort also extends to most of the new methods introduced on Object, including Object.keys and Object.getOwnPropertyNames. Neither method will coerce primitive arguments into Objects – in fact they will both throw a TypeError:

//Chrome, Safari, FF4, IE9

Object.keys("potato");
//TypeError: Object.keys called on non-object

Object.getOwnPropertyNames("potato");
//TypeError: Object.getOwnPropertyNames called on non-object

Thus, the following examples represent one of the few scenarios outside of Strict Mode where it makes sense to use the new String construction. Note that when either method is passed a string, the index name of each character is included.

//Chrome, Safari, FF4, IE9

Object.keys(new String("potato"))
//["0", "1", "2", "3", "4", "5"]

Object.getOwnPropertyNames(new String("potato"))
//["0", "1", "2", "3", "4", "5", "length"]


Wrap Up

Once they are available across all the major browsers Object.keys and Object.getOwnPropertyNames will make object/hash manipulation leaner and more powerful by plugging a major hole in the JavaScript Object API. Moreover as the line between Arrays and regular Objects blurs (aided by custom getters and setters) we’re likely to see a growth in generic “array-like” objects which enjoy the best of both worlds – non-numeric identifiers and access to the rich API set defined by Array.prototype. EcmaScript 5 has apparently pre-empted this trend by introducing the generic method, defined by one type but useable by any.

There’s a seismic shift under way – be ready for it!

Further Reading

ECMA-262 5th Edition
Object.keys(obj)
Object.getOwnPropertyNames(obj)

 

Learn why developers are gravitating towards Node and its ability to retain and leverage the skills of JavaScript developers and the ability to deliver projects faster than other languages can.  Brought to you in partnership with IBM.

Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}