Rethinking JavaScript Object Enumeration

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) -> " + 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)

About these ads

34 thoughts on “Rethinking JavaScript Object Enumeration

  1. Pingback: HTML all you need to know» Blog Archive » Rethinking JavaScript Object Enumeration | JavaScript, JavaScript

  2. Pingback: HTML Scripts Tips and Secrets » Blog Archive » Rethinking JavaScript Object Enumeration | JavaScript, JavaScript

  3. Pingback: Rethinking JavaScript Object Enumeration | JavaScript, JavaScript – javascript - dowiedz się więcej!

  4. I wonder how long we’ll have to wait for Object.getOwnPropertyNames(obj) to be widely implemented enough to user it without worry.

    Also, I’m having some difficulty finding an example of when Object.getOwnPropertyNames(obj) would allow for some new use that would be useful.

    Thanks for the overview,
    Allain

  5. Pingback: Дайджест №2 | ByteFrames

  6. @Martin Glad you liked it!

    @Allain For tech-savvy consumer apps not long at all – FF4 and IE9 will both support it. Not sure about Opera – @MikeTaylr? For enterprise apps that need to support IE back editions – forever – and no JS library can help you (evil laugh!)

    Possible usage off the top of my head – debugging utils such as tracers that apply a function to every function property of a specified object.

    e.g. traceAll(Math)

  7. Useful article. I’ve started using some of the new ECMAScript functions as provided by underscore.js. Not all of them are provided, but the supported ones are delegated to the native version if available.

  8. Excellent article, as always.

    Why are the new methods only implemented on ‘Object’, and not ‘Object.prototype’?

    • Jason – good question. There is an unwritten rule amongst JavaScript developers to not augment Object.prototype – mainly because for-in was traditionally the only way to iterate a regular Object and the risk was that user defined Object.prototype properties would bleed through every iteration. However this doesn’t really explain the ES5 spec because 1) built in properties are not enumerable 2) ES5 does define some functions on Object.prototype (e.g. hasOwnProperty and isPrototypeOf).

      Prototype.js wrote the first popular implementation of Object.keys (and some other nice Object augmentations) and they made it a property of Object constructor. ES5 may have been following their lead.

      Someone else may have a better answer

      • I wondered this too. But, it’s easy enough to fix. And by fix I mean monkey patch!


        Object.prototype.keys = function() {
        return Object.keys(this)
        };

        Now I can do this.


        var obj = {foo : 42, bar : true, baz : 'Hola, mundo!'};
        obj.keys().forEach(function(key) {
        console.log(key + ' -> ' + obj[key]);
        });

        Of course, I sure would love to be able to get rid of those parens after keys.

      • Very curious about this, so asked on JSMentors. We’ll see what the experts have to say :)

    • I’ve heard two reasons:

      1. Since the global object is (naturally) an object, an annoying side-effect of augmenting Object.prototype is that those new properties get added to the global scope as well. So to add {}.keys would define a global keys function.

      This would be both annoying and inconvenient — in order to retrieve the keys defined on the global object (a not-too-farfetched use case), you’d have to avoid defining a global function called keys because it’d shadow the one you need.

      2. It’s feasible to write a JavaScript-only version of Object.keys for browsers that don’t implement it. ES5 benefits from this approach because it lets us use some of the best stuff now, without having to wait for full ES5 support in all browsers.

      It wouldn’t be feasible to write a JavaScript-only version of a theoretical {}.keys function, since that would require augmenting Object.prototype. (Those same browsers also lack ES5′s Object.defineProperty or any other way to hide property names from enumeration.)

  9. hi,
    i am a chinese , i am not good at english.
    i like your articles very much.

    i have a question want to ask you. can you help me? thanks

    var a = function b() { alert(a == b) }

    a(); // true

    alert(typeof b); // undefined

    in chrome, i got this effect.

    not i don’t know why b is not undefined in the function. where b is defined on?

    • Hi and thanks for the compliment!

      the right hand side of the first line is a function expression (FE) you haven’t actually assigned anything to b

      It is really the same as saying
      var a = function() {…}

      (you are naming the FE ‘b’ but this is just annidentifier for the FE – it’s not assigned to anything.

      To make typeof b return function you must either use a function declaration (FD)…

      function b() {…};
      var a = b;

      …or use an explicit variable declaration…

      var a = function(){…};
      var b = a;

      For more info see https://javascriptweblog.wordpress.com/2010/07/06/function-declarations-vs-function-expressions/

      Hope this helps

  10. I’m sorry, I have not express clearly.
    when “typeof b == ‘undefined’” is true in global code, b is not blinds with global execution context.
    why “a == b” is true when execute a();
    in your article you recommends to me, you said: The function name is visible within it’s scope and the scope of it’s parent.
    where “b” is blinds with ?
    thank you ask my question.
    because i am not good at english, when I read “Lexical Environments”(ECMA10.2), I was confuse at the concept of declarative environment records and object environment records.
    would you like to write a artcile about that subject when you have enough spare time.

  11. Pingback: Rethinking JavaScript Object Enumeration | Javascript | Syngu

  12. Pingback: Finding object keys in Javascript - Programmers Goodies

  13. This is a little late, but there is an easy way to extend native objects in JavaScript taking advantage of ECMAScript 5:

    extend = function(obj, prop) {
    Object.keys(prop).forEach(function(val) {
    if (prop.hasOwnProperty(val)) {
    Object.defineProperty(obj, val, {
    value: prop[val],
    writable: true,
    enumerable: false,
    configurable: true
    });
    }
    });
    };

    This would allow you to extend an object’s prototype using the ‘enumerable’ property set to false so that the object’s prototype only iterates it own properties, solving that headache.

    extend(Object.prototype, {
    whatever : function(blah) {
    // do stuff with blah
    }
    });

  14. Pingback: JSSpy » Rethinking javascript object enumeration

  15. I don’t think you can say:

    “EcmaScript 5 has apparently pre-empted this trend by introducing the generic method, defined by one type but useable by any. ”

    ES5 didn’t introduce generic methods, they are in ECMA-262 ed 3. Also, they aren’t “useable” by any type (where type is Type per ECMA-262), especially in ES5, which does not coerce the *thisArg* to an object, it’s passed as–is.

  16. Pingback: Problems with extending ES5 Array capabilities to support regular Objects | BlogoSfera

  17. Pingback: Problems with extending ES5 Array capabilities to regular Objects

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s