Exploring JavaScript for-in loops

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 -&gt; "1,2,3,foo"
//Other browsers -&gt; "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]](name);

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

About these ads

51 thoughts on “Exploring JavaScript for-in loops

  1. Pingback: Exploring JavaScript for-in loops

  2. Pingback: Twitted by theystolemynick

  3. Pingback: Tweets that mention Exploring JavaScript for-in loops | JavaScript, JavaScript -- Topsy.com

  4. Pingback: HTML Scripts Tips and Secrets » Blog Archive » Exploring JavaScript for-in loops | JavaScript, JavaScript

  5. Pingback: HTML Scripts Tips and Secrets » Blog Archive » Exploring JavaScript for-in loops | JavaScript, JavaScript

  6. In the first code listing, you are pushing the object being iterated-upon onto the myKeys array, where I think you mean to push the properties of that object as it’s being iterated.

  7. Pingback: HTML all you need to know» Blog Archive » Exploring JavaScript for-in loops | JavaScript, JavaScript

  8. Pingback: Exploring JavaScript for-in loops | JavaScript, JavaScript

  9. Pingback: Exploring JavaScript for-in loops | JavaScript, JavaScript » Web Coding Unravelled

  10. A better alternative for your third example in section `1.` would be

    var str = ‘hello!’, spreadOut;
    spreadOut = str.split(”).join(‘ ‘);
    document.write(spreadOut); //outputs “h e l l o!”

  11. Great article Angus!

    If you want to include ES5, don’t forget Object.keys and Object.getOwnPropertyNames.

    Never knew that any left hand side expression could be in a for-in loop, but I guess it makes sense. Now to see what cool, concise things I can do with it…

    • Thanks Nick

      >>Never knew that any left hand side expression could be in a for-in loop, but I guess it makes sense. Now to see what cool, concise things I can do with it…

      Sadly if they had allowed commas in leftHandSideExpression it might have been cooler, more concise, as in:

      for ((while (arr[i++]), arr[i]) in obj)

      but @kangax tweeted a cool one:

      Object.defineProperty(this, ‘log’, {set: console.log.bind(console)});
      for (log in window);

  12. Notice also, that “side-effect” properties, e.g. added/deleted during the enumeration are also not specified to be enumerated (try in different implementations, I remember some version of IE have/had a it’s own behavior).

    The current reason of not specifying the exact order is of course in optimization. Dense arrays are stored as real C-arrays in JS. And when some non-integer property is added, they are rebuilt into a (less-, non-optimized) hash-table.

    For example in Firefox:

    a = []
    a[2] = 1
    a[1] = 1
    a[0] = 1
    for (i in a) alert(i)

    will alert 0, 1, 2.

    Now if you we have

    a["x"] = 1

    we get 0, 1, 2, “x”

    However, if we start out with x, then we have:

    a = []
    a["x"] = 1
    a[2] = 1
    a[1] = 1
    a[0] = 1

    Will alert x, 2, 1, 0

    Andreas Gal figured this out in es-discuss thread: https://mail.mozilla.org/pipermail/es-discuss/2010-December/012464.html

    Dmitry.

    • Thanks Dmitry,

      You bring up an important issue with sequence of side-effect properties. I would like to investigate further.

      As for array enumeration sequence – yes it’s a rats nest – notice in my article I deliberately avoided going there by specifically talking about ‘non-array’ objects in the sequence section. ;-)

      I think it’s ok to not worry too much about array iteration sequence for the case of arrays with non numeric keys since it is well established that using for-in over such arrays is bad practice. Indeed in the chrome sequence bug I notice the argument is made that safari and ff disagree over sequence but they use an array with non numeric keys as an example – which seems to miss the point (deliberately ;-)?). There are plenty of good iteratation techniques for arrays – so the sequence of for-in iteration over arrays should not be an issue for developers.

      Having said all that it is sometimes interesting to explore such browser differences because, as you suggest, it offers insight into the workings of the respective JS engines.

      Angus

  13. Pingback: JavaScript Magazine Blog for JSMag » Blog Archive » News roundup: jspp, node-scraper, Nike Better World

  14. Pingback: La veille du week-end (douzième) | LoïcG

  15. Pingback: Exploring JavaScript for-in loops | Javascript | Syngu

  16. Pingback: Javascript for AJAX | Prometheus

  17. Pingback: Analizando el bucle for-in en Javascript | EtnasSoft

  18. Pingback: The JavaScript "for...in" statement - don't leave home without it | Kevin Chisholm Blog

  19. Pingback: ModernNerds » Excellent JavaScript resources for beginners to veterans

  20. Pingback: Pętla For … in « dorofiejczyk

  21. Pingback: The JavaScript for...in statement - enumerating object properties | Acme Travel

  22. Pingback: The JavaScript for...in statement - enumerating object properties | Kevin Chisholm Blog

  23. Pingback: JSHint. Повышаем качество JavaScript кода. | GNU/Linux в Новосибирске

  24. Pingback: dev 4 WP testing - The JavaScript for…in statement – enumerating object properties

  25. Pingback: dev 4 WP testing - The JavaScript “for…in” statement – don’t leave home without it

  26. Pingback: Kevin Chisholm – Blog - The JavaScript “for…in” statement – don’t leave home without it

  27. Pingback: JSLint | cattail's blog

  28. The line `for (var LeftHandSideExpression in objectExpression)` is incorrect, the `var` keyword is a syntax error.

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