Topic on Manual talk:Coding conventions/JavaScript

MarkTraceur (talkcontribs)

In the series of "ambiguous code styles that really shouldn't be ambiguous", what's the best form of loop in the situation of looping through an array?

I'm seeing all three of

for ( var i = 0; i < arr.length; i++ ) {
    ....
}

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

$.each( arr, function ( i, item ) {
    ....
} );

Input appreciated!

Brooke Vibber (talkcontribs)

for (var i in arr) has to be armored with checks for arr.hasOwnProperty(i) to protect against brokenness if frameworks are present that modify the Array prototype. Bleh!

Personally I prefer to use $.each() most of the time:

  • functional style / iterator feels nicer than counting manually
  • you get a real function scope -- for instance you can safely use i in a lambda function inside your loop, which you can't with the first two forms

However it is a little more expensive, so performance-critical paths may prefer the traditional for loop with iterator variable and comparison to length.

Nischayn22 (talkcontribs)

I myself prefer $.each(), mostly because of its functional nature. Besides, I love the jQuery way.

Krinkle (talkcontribs)

There is no one convention for looping in general, because there are different solutions for different situations.

Looping over an array

When it comes to looping over an array (or array-like objects such as jQuery objects and Argument objects) however, then there really is only one good way, and that's a for loop with a number counter. And the reason is that you want to loop over the the indexes, not over all properties unconditionally.

Example of dump for a jQuery object ($('#content');):

{
    selector: '#content',
    context: HTMLDocument.
    length: 1,
    0: HTMLDivElement
}
/// inherits from jQuery.prototype
/// inherits from Object.prototype

You wouldn't want to loop over all properties, only the indices (based on the length). Even a hasOwnProperty filter won't help you here, because they are all "own" properties.

The other case is when there is a sparse array that may contain gaps, in most cases the intent is to loop over all of them, and yield undefined for gaps. Another case is that in JavaScript everything is either a primitive (string, number, boolean, ..) or an object. Arrays are objects. And as such, keys of properties are strings, which can cause issues when accessed as such. A for-in loop will give you the internal key values, which are strings (as they should be).

e.g. the following will never log the "Success!" message:

var key,
    arr = ['foo', 'bar'];

for ( key in arr ) {
    console.log( 'Key:', $.toJSON( key ), 'Value:', $.toJSON( arr[key] ) );
    if ( key === 1 ) {
        console.log( 'Success! Found the second item (should be "bar"):' + arr[key] );
    }
}

Because arr (as being an object) is { "0": 'foo', "1": 'bar' }, and "1" !== 1.

Looping over an object

If you do want to loop over all properties of an object then, of course, there is nothing wrong with a for-in loop.

var obj = {
    'foo': 'Hello Foolifications'
    'bar': 'Rabarber Barbara\'s bar'
};

for ( key in arr ) {
    console.log( 'Key:', $.toJSON( key ), 'Value:', $.toJSON( arr[key] ) );
    if ( key === 1 ) {
        console.log( 'Success! Found the second item (should be "bar"):' + arr[key] );
    }
}

If you explicitly need to exclude inherited properties (e.g. you have an instance of FooBar that inherits from FooBar.prototype, and you want to clone it):

var clone, key,
    hasOwn = Object.prototype.hasOwnProperty,
    foo = new AwesomeFoo( /* .. */ );

/* .. */

clone = Object.create(AwesomeFoo.prototype);

for ( key in foo ) {
    if ( hasOwn.call( foo, key ) {
        clone[key] = foo[key];
    }
}

So, what about $.each ? Well, $.each is basically the same as a for-in loop ($.each does not filter hasOwn), with the difference that it has its own scope. In most cases you probably don't need it, but if you need to do a lot of loops and want to avoid conflicts with the different counter or key variables, then using a scope can be helpful. Especially in nested loops.

Danwe (talkcontribs)

It should be mentioned that $.each is checking whether the given object/array has a length field. If so, then each will only iterate over the "numeric" fields smaller than "length", no matter whether the field's value is undefined or not.

ESanders (WMF) (talkcontribs)

@MarkTraceur For a simple array loop I would use ES5's arr.forEach. The var i = 0 style is marginally faster for very large loops, and mostly just in older browsers (e.g. IE11).

$.each will currently trigger a linter error suggesting you use arr.forEach instead; there's no need to use jQuery when the plain JS versions is just as concise.