« Jacob Kaplan-Moss at Mahalo | Main | memcache-top patch to see curr_items »
Wednesday
Dec222010

Tips.js - Lesson 1: For-in Caveats

JS has several different looping statements, many of which we know and love: for, while, and do-while. Now I know what you all are thinking: "But, Rich, JS is clearly sub-par to all of our more modern languages because it doesn't have a for-each statment". You are (in)correct. JS has a sort of for-each statement, which we like to call the for-in statement. Our friend for-in has the structure of for (var <key> in <object>). Recall that all objects in JS are basically key/value stores, so what this loop does is iterate through all of the runtime keys (properties that are bound to the object by your code) on the given object, so if you were to have an Object obj and you added the properties x, y, and z, the for-in loop would iterate through those keys x, y, and z.

Sounds good, but there's a problem. Recall that JS is a prototype-based object oriented language, meaning all objects have a prototype property which references another object from which it will inherit properties from, and that object will have a prototype to another object, etc. The problem with for-in statements is that they do not discriminate between properties of the object and properties of the object's prototype chain. Well, okay, it's not actually a problem as this is the ECMAScript standard behavior of the for-in statement. The problem is that few people are actually aware of this behavior.

If you crack open a firebug/chrome console, and try it out, you'll see that if you have an object foo whose prototype object is another object bar where foo has a property fizz and bar has a property buzz, then the following will happen:

var foo = { fizz: 'asdf' };
var bar = { buzz: 'qwerty' };

foo.__proto__ = bar;

for (var x in foo) {
    console.log(x); //prints "fizz" and "buzz"
}

[Disclaimer: It's considered bad form to directly modify the __proto__ attribute of an object. This is just for demonstration purposes] You'll also notice that if you look at bar's prototype, you'll see the native/host JS Object, which has a bunch of properties that the for-in statement seems to ignore. For-in only cares about properties that are assigned at runtime, meaning any property we assign in our scripts, for example Array.prototype.someFunction = function() {};. There are several high-profile JS libraries that do things like this for a living (ever wonder why the Prototype JS library is called "Prototype"?). Now consider if you try using a for-in loop with an array. Arrays in JS are just objects. Do a console.dir on an Array object and you'll see that it is simply and object with keys that map to the index of the array data (the object's property of "0" is simply a reference to the zeroth index of the array, and this is why using array accessors [] work when you access object keys; there is really no difference in accessing an array index and accessing an object property). Because of this, doing a for-in statement on an array will iterate through all of the elements in your array as you would expect, but if you ever modify the Array object's prototype, that modification is considered a runtime modification and will be iterated in the for-in loop.

Bottom line is, if you never actually do any native/host object prototype modifications, you don't have to worry about this issue. I should also mention that several browsers support an ECMAScript 5 standard Object function called keys, which when called returns an array of all keys on the given object that completely disregards the prototype chain. This is basically a native way to do the following:

for (var x in foo) {
    if (foo.hasOwnProperty(x)) {
        console.log(x);
    }
}

I'm not a big fan of using the above method outside of native code, as the loop will still execute through all of the properties of the prototype chain with an added conditional execution to determine if the property belongs to foo, and if you don't really know how many runtime properties were added up the prototype chain, this has potential to be inefficient (but if you do actually know that you're not doing any native/host object prototype modifications, you can feel free to use for-in at your leisure. Just understand that it can come back and bite you later on down the line if you're not careful)

Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>