In JavaScript, enumeration across regular (non-Array) Objects is often more painful than it should be. Arrays are merrily dised 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 .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.…
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.…
//Chrome, Safari, FF4, IE9 Object.getOwnPropertyNames(String.); //["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.. 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)
Excellent post, thank you!
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
@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)
It should be noted that until ’s most recent version (1.7), its Object.keys() returned an object’s own keys as well as keys from the chain and would also overwrite the native Object.keys() if it exists.
Also your `Object.keys` implementation overlooked throwing an error if `obj` is a primitive.
Fixed – thanks!
Object.keys() should allow function objects too.
right – done!
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.
Another nitpick for you, Object.keys(null) should also throw a TypeError.
Might be worth having a look at Tom Robinson and Kris Kowal’s es5-shim project, Object.keys is here: https://.com/kriskowal/es5-shim/blob/master/es5-shim.js#L400-446
Thanks Sami
Excellent article, as always.
Why are the new methods only implemented on ‘Object’, and not ‘Object.’?
Jason – good question. There is an unwritten rule amongst JavaScript developers to not augment Object. – mainly because for-in was traditionally the only way to iterate a regular Object and the risk was that user defined Object. 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. (e.g. hasOwnProperty and isOf).
.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 !
Object..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.
is that those new properties get added to the global scope as well. So to add{}.keys
would define a globalkeys
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 augmentingObject.
. (Those same browsers also lack ES5’sObject.defineProperty
or any other way to hide property names from enumeration.)I second the vote for es5-shim. Also useful: The ECMAScript 5 compatibility table shows what is supported on which browsers.
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
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.
Since version 11.60 Opera supports these functions too, and not only that but “Implemented full support for ECMAScript 5.1” (http://www.opera.com/docs/changelogs/windows/1160/).
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 using the ‘enumerable’ property set to false so that the object’s only iterates it own properties, solving that headache.
extend(Object., {
whatever : function(blah) {
// do stuff with blah
}
});
I like reading an article that will make people think.
Also, thanks for permitting me to comment!
Reblogged this on rg443blog and commented:
JavaScript Object Enumeration
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.