Matt Snider JavaScript Resource

Understanding JavaScript and Frameworks

Saturday, March 1, 2008

Using For In Loops Safely

Many of the JavaScript Frameworks and Libraries used today have the bad habit of attaching methods to the Prototype of Array and Object. While, in theory it would be really nice to be able to extend these Object, in practice, doing so breaks “For … in” loops. For example, if you include Json.js” in your project, you have the power to easily work with JSON objects. However, it attaches “toJSONString” method to “Array.prototype”. So when you use the “For … In” loop on any array, one of the keys will be the bogus key “toJSONString”.

This is frustrating as “For … In” loops are the only way, in JavaScript, that you can iterate on the keys of an Object and associative Arrays. Most Frameworks that modify these objects also add functions that allow you to work around this limitation, but then you become dependent on them to do any Array iterations. Douglas Crockford (see JSLint) and other JavaScript guru’s recommend keeping a list of keys that you want to ignore and always filtering your “For … In” loop with it. This is one way to fix the problem, but what does one do when they don’t know the keys or Array/Object prototypes change dynamically as your JavaScript executes, such as when you use YUI to extend Object and it attaches the “constructor” variable to Object’s prototype.

Often you need the ability to dynamically determine what keys to filter before each “For … In” loop. To accomplish this, I have developed the following function:

Example 1: GetKeysToIgnore

var getKeysToIgnore = function() { var tarr = [], // empty array tobj = {}, // empty object tdom = document.getElementsByTagName(”body”), // empty nodelist keys = {’item’: true, ‘length’: true};// default keys to ignore // iterate on the native array object for (var k in tarr) {keys[k] = true;} d // iterate on the native object object for (var l in tobj) {keys[l] = true;} // iterate on the nodelist, but don’t ignore indices for (var m in tdom) { if (isType(parseInt(m, 10), ‘number’)) {continue;} keys[m] = true; } return keys; };

This method creates an instance of each of the three type of iterable JavaScript objects: Array, Object, NodeList (especially important of Safari). Since, these objects are empty, they contain only the keys for elements that have been attached to their Prototypes. Therefore, when we iterate on it we can create a hash Object of all these keys. The default list is just some keys that you want to reserve for your project, which may or may not always be attached to one of these 3 objects.

Call “getKeysToIgnore” before any “For … In” loop and then filter these keys out:

Example 2: Filtering

var test = { key1: 1, key2: “1223′, key3: new Date() }; var ikeys = getKeysToIgnore(); for (var key in test) { // key is not in our ignore hash if (! ikeys[key]) { // put your logic here } }

Using this technique, you could modify the “batch” Function of Core.js to use this technique, thereby supporting both Array and Object iteration:

Example 3: Batch

Core.batch = function(o, fn) { var args = Array.prototype.slice.apply(arguments, [2]); var ikeys = getKeysToIgnore(); args.unshift(null, null); // iterate on the items, executing the function, and stoping when function returns true or visited all elements for (var key in o) { // key is not in our ignore hash if (! ikeys[key]) { args[0] = o[i]; args[1] = i; var rs = fn.apply(this, args); if (rs) { return rs; // allows the batch function to return a found result } } } };

While this is a little more powerful than the Array based “batch” Function, it is a little slower because the ikeys array must be created each time before batching. Something to think about is how to improve/cache getKeysToIgnore better and increase the performance of this Function.

posted by Matt Snider at 12:46 pm  

8 Comments »

  1. We should finally let the erroneous notion of “Associative Array” in JavaScript die. There is no such thing, and it’s irresponsible to keep using the term. Arrays should never, ever be used to store name-value pairs - they should only be used to store items indexed to an integer. Object literals serve the purpose that Associative Arrays serve in other languages.

    With this discipline, it’s perfectly acceptable to extend Array.prototype since you shouldn’t be iterating over arrays using for .. in anyway. Use a conventional for or while loop instead, and leave for .. in to object literals. (Of course, it’s still bad practice to extend Object.prototype for this reason.)

    Comment by David Golightly — March 1, 2008 @ 1:42 pm

  2. Asociative arrays = objects :)

    anyways i find it easyer and faster to test the type of the element in the loop depending on the situation.
    for (var i in obj) {
    if (typeof obj[i] != ‘function’) {
    }
    }

    I guess your code is very usefull when u nead to iterate thru methods also.

    Comment by Florin — March 1, 2008 @ 3:04 pm

  3. When I am looking for a JavaScript library, I’m almost always instantly turned off if I find they’ve done something to extend native objects. It’s a red flag for me that they don’t necessarily care how their code interacts with another library or your hand coded functions. I understand the urge to fill in the gaps you might feel JavaScript has left out, but there’s other ways around that that work well with other code.

    As an example. I want any object I use in my library to be able to find their own attributes.
    So here’s how I might go about doing that…

    var ExtendedObject = function(obj) {
    for(var attr in obj) {this[attr] = obj[attr];
    }

    ExtendedObject.prototype.findAttribute = () {
    …//Put code here
    }

    This allows me to create my own extended object like this.

    var obj = new ExtendedObject({test: ‘test’});

    The native object has not been changed, so someone else is free to use the object however they wish. You can determine if the object is of type ExtendedObject by using the instanceof operator. Additionaly, you’d probably want to have an ExtendedObject.prototype.stripObject function that filters your extended object.

    The only major drawback is that you have to use “new ExtendedObject” (or whatever you choose to call it), everytime you want to create an object, rather than just “{}”. It’s only a drawback in size, though…in return you get code that works well with others.

    Comment by MillsJROSS — March 3, 2008 @ 7:02 am

  4. Thanks for your comments everyone.

    David, although, I agree that Associative JavaScript Arrays technically don’t exist (they are simply objects) and should never be used. It is still possible to syntactically create them.

    Florin, you are absolutely right, it is much faster to know what you are testing for and just test against it. I however, have used Functions as members of an Array on more than one occasion.

    Mills, I like your approach and will have to explore it more.

    Comment by Matt Snider — March 4, 2008 @ 9:08 am

  5. Recently we’ve gotten away from extending the Object at all.

    The new YUI JSON singleton provides the same functionality without adding Object.toJsonString (Instead: YAHOO.lang.JSON.parse() or .stringify())

    Although, safety with for-in loops should always be considered (in JS anyways).

    Comment by Dimitry — March 4, 2008 @ 12:33 pm

  6. The first time I play with XMLHttpRequest, I use for..in loops with childNodes and getElementsByTagName, like this:

    for (var i in items) { ... }

    Then it always gave me an error, and I don’t understand why.

    I later know that i was “length”.

    Very good technique. Too bad I don’t do so much OOPs.

    Comment by the DtTvB — March 13, 2008 @ 9:49 pm

  7. I liked the content of the blog. can i use the link as an educational reference?

    Comment by Vijaykant — March 21, 2008 @ 4:13 am

  8. Vijaykant, you can link to anything you want. I only ask that if you copy anything from this blog, you also reference it.

    Comment by Matt Snider — March 21, 2008 @ 8:29 am

RSS feed for comments on this post. TrackBack URI

Leave a comment

Powered by WordPress