Exploring JavaScript for-in loops (original) (raw)
The for-in
loop is the only cross-browser technique for iterating the properties of generic objects. There’s a bunch of literature about the dangers of using for-in
to iterate arrays and when to apply the hasOwnProperty
filter, but beyond that, documentation of this ubiquitous construct is surprisingly patchy. This article attempts to fill some gaps, I hope its useful.
The Basics
The ES 5 specification details two distinct syntaxes for the for-in
statement:
1. for (var variable in objectExpression) {statement}
This is the familiar format. Any expression that evaluates to an object may be used as the objectExpression. If a primitive is supplied it will be coerced to an object. The properties of this object are iterated. On each iteration the name of the property is assigned to the declared variable and the statement (if present) is evaluated.
var myObj = {a: 1, b: 2, c: 3}, myKeys = [];
for (var property in myObj) { myKeys.push(property); }
myKeys; //['a','b','c'];
The variable can optionally be defined outside of the for-in
production. The curly brackets are only required if the statement spans multiple lines and the statement itself is optional. Therefore the following code is also valid – though not terribly useful unless you are interested in recording the name of myObj’s “last” property (more about iteration sequence later).
var myObj = {a: 1, b: 2, c: 3}, lastProperty;
for (lastProperty in myObj);
lastProperty; //"c";
Here’s another example. In this case the objectExpression resolves to a primitive:
var str = "hello!", spreadOut = "";
for (var index in str) { (index > 0) && (spreadOut += " ") spreadOut += str[index]; }
spreadOut; //"h e l l o !"
Note that as with all property names, the indexes in the above example are actually strings – so we cannot do a simple “truthy” test on line 5. Later on we’ll see why Strings and Arrays are not always good candidates for for-in
iteration.
2. for (LeftHandSideExpression in objectExpression) {statement}
This interesting syntax is seldom documented (MDC gives it no mention). In ECMAScript terms a LeftHandSideExpression is any expression that resolves to a property reference (think anything that can go on the left hand side of an assignment). On each iteration, the name of the next property gets assigned to the evaluation of the LeftHandSideExpression. It’s perfectly valid for the LeftHandSideExpression to resolve to a different reference on each iteration. Occasionally this is useful – even elegant – for example getting an array of property names is now a breeze:
var myObj = {a: 1, b: 2, c: 3}, myKeys = [], i=0;
for (myKeys[i++] in myObj);
myKeys; //['a','b','c'];
Which properties are iterated?
This requires some knowledge of internal JavaScript properties. Objects are collections of properties and every property gets its own standard set of internal properties. (We can think of these as abstract properties – they are used by the JavaScript engine but they aren’t directly accessible to the user. ECMAScript uses the [[_property_]] format to denote internal properties).
One of these properties is [[Enumerable]]
. The for-in
statement will iterate over every property for which the value of [[Enumerable]]
is true. This includes enumerable properties inherited via the prototype chain. Properties with an [[Enumerable]]
value of false, as well as shadowed properties (i.e. properties which are overridden by same-name properties of descendant objects) will not be iterated.
In practice this means that, by default, for-in
loops will pick up every non-shadowed, user-defined property (including inherited properties) but not built-in properties. For example Object’s built-in functions (such as toString
) will not be enumerated.
This also means that if you are in the habit of augmenting the prototypes of built-in objects, then your custom extensions will also show up:
var arr = ['a','b','c'], indexes = [];
Array.prototype.each = function() {/*blah*/};
for (var index in arr) {
indexes.push(index);
}
indexes; //["0", "1", "2", "each"] whoops!
Some frameworks (e.g. Prototype.js and Mootools) add a lot of custom prototype augmentation and using for-in
to iterate Arrays and Strings is generally considered a bad idea. Using a regular for
loop is a good alternative for Array and String iteration. In addition, ES5 defines a bunch of custom Array iterators (forEach
, map
etc). Unfortunately non of these alternate iteration strategies work with regular Objects – which is why its considered very bad practice to augment Object.prototype
.
The “DontEnum” bug
IE<9 suffers from a serious iteration quirk whereby properties that shadow built-in (and therefore non-enumerable or [[DontEnum]] in ES3 parlance) properties will also not be enumerated.
var obj = {
a: 2,
//shadow a non-enumerable
toString: "I'm an obj"
},
result = [];
for (result[result.length] in obj);
result;
//IE<9 -> ["a"]
//Other browsers -> ["a", "toString"]
(Thanks to @kangax for the reminder and @skilldrick for the neat variation on for (result[i++] in obj);
Can I prevent certain properties from being iterated?
Yes. There are a couple of standard techniques for filtering out unwanted members from our for-in
loops:
1. Object.prototype.hasOwnProperty
This function will invoke the property’s internal [[GetOwnProperty]] method to determine whether the given property is defined directly on the object (instead of somewhere in the prototype chain).
var arr = ['a','b','c'], indexes = [];
Array.prototype.each = function() {/*blah*/};
for (var index in arr) {
if (arr.hasOwnProperty(index)) {
indexes.push(index);
}
}
indexes; //["0", "1", "2"]
JSLint expects you to always wrap the body of a for-in
with an if
statement even when iterating a regular object (never mind that you could just as easily assert the condition with an &&
instead of an if
!)
If you’re paranoid that you or someone else might override the local definition of hasOwnProperty
you can invoke the prototype reference directly
//snip... for (var index in arr) { if (Object.prototype.hasOwnProperty.call(arr, index)) { indexes.push(index); } }
2. Object.defineProperty
ES5 introduces a new method on Object which allows properties to be defined with custom internal property settings (not supported in FF<4 and IE<9)
var obj = {};
Object.defineProperty( obj, "value", { value: true, writable: false, enumerable: true, configurable: true });
We can leverage this to set our own value for [[Enumerable]] allowing us to hide custom prototype augmentations from the for-in
iterator
var arr = ['a','b','c'], indexes = []; Object.defineProperty(Array.prototype, "each", { value: function() {/blah/}, writable: false, enumerable: false, configurable: false });
for (var index in arr) { indexes.push(index); }
indexes; //["0", "1", "2"]
What is the iteration sequence?
The ECMA standard does not specify an enumeration order but the de facto standard for non-array objects is to enumerate properties according to the order of their original assignment.
var obj = {a: 1, b: 2, c: 3}, result = [];
obj.e; //referenced but not assigned obj.f = 'bar'; //1st assignment obj.e = 4; obj.dd = 5; obj.f = 'foo'; //2nd assignment
for (var prop in obj) { result.push(prop); }
result.toString(); //"a,b,c,f,e,dd"
However there are currently a couple of important exceptions you should be aware of:
Deleting properties in IE
In IE deleting a property and then redefining it does not update its position in the iteration sequence. This contrasts with the behaviour observed in all other major browsers:
var obj = {a: 1, b: 2, c: 3}, result = [];
delete obj.b; obj.b = 4;
for (var prop in obj) { result.push(prop); }
result.toString(); //IE ->"a,b,c" //Other browsers -> "a,c,b"
Numerically named properties in Chrome
Chrome browsers process numerically named keys first and in numeric sequence not insertion sequence.
var obj = {3:'a', 2:'b', 'foo':'c', 1:'d'}, result = [];
for (var prop in obj) { result.push(prop); }
result.toString(); //Chrome -> "1,2,3,foo" //Other browsers -> "3,2,foo,1"
There’s a bug logged for it together with a gazillion comments forming a raging back and forth argument as to whether it should be fixed. To my mind this is a bug that needs fixing. Sure properties of regular objects are by definition unordered, and yes ECMA has not yet defined a standard – but as John Resig and Charles Kendrick point out, the lack of an ECMA standard is no excuse – standards generally follow implementation and not vice versa – and in this case chrome is out of line.
The in
operator
This nifty cousin of for-in
uses the internal [[HasProperty]] method to check for the existence of a named property in a given object:
propertyNameExpression in objectExpression
In pseudo code terms it works something like this:
var name = //resolve [propertyNameExpression]; var obj = //resolve [objectExpression];
return obj.[HasProperty];
Here’s some usage examples:
var obj = {a:1, b:2, c:undefined, d:4}, aa = {};
'b' in obj; //true 'c' in obj; //true ('undefined' but still exists) 'e' in obj; //false (does not exist)
delete obj.c; 'c' in obj; //false (no longer exists)
obj.e; 'e' in obj; //false (referenced but not assigned)
//resolving expressions aa.o = obj; aa.a = 'a'; aa.a in aa.o; //true
Notice how 'c' in obj
returns true even though the value of o.c
is undefined
. The internal method [[HasProperty]] will return true for any assigned property regardless of value. This is useful for distinguishing those properties that are deliberately assigned undefined
from those that simply do not exist.
Like the for-in
loop, the in
operator will search in the object’s prototype chain. Unlike the for-in
loop, the in
operator does not distinguish enumerable and non-enumerable properties:
var arr = [true,false,false];
1 in arr; //true 'slice' in arr; //true 'toString' in arr; //true
And that’s about all. Feel free to comment with suggestions, omissions or complaints 😉
Further Reading
Resig, John: JavaScript in Chrome
V8 Bug Log: Wrong order in Object properties interation [sic]
ES 5 Discussion: Yet more ambiguities in property enumeration
ECMA-262 5th Edition:
8.6.1 Property Attributes (includes [[Enumerable]])
8.12.1 [[GetOwnProperty]]
8.12.6 [[HasProperty]]
11.2 Left-Hand-Side-Expressions
11.8.7 The in
Operator
12.6.4 The for-in
Statement
15.2.4.5 Object.prototype.hasOwnProperty