Rethinking JavaScript Object Enumeration (original) (raw)

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

Object.getOwnPropertyNames(Math).forEach(function(e) { if((typeof Math[e] == 'function') && (Math[e].length == 1)) { console.log("Math." + e + "(10) -> " + Mathe); } }); //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)