2009-04-16

syntaxes for iterating arrays in JavaScript

JavaScript lets you iterate arrays using the for each...in statement:
for each (var item in [1, 2, 3]) alert(item);

JavaScript 1.6 added the Array.forEach method:
[1, 2, 3].forEach(function(item) { alert(item) });

I've always preferred Perl's statement modifiers, though, for the popular English-like order of their clauses ("do this for each of those"):
print $_ foreach (1, 2, 3);

JavaScript 1.7 added array comprehensions for array initialization:
var squares = [item * item for each (item in [1, 2, 3])];

I just realized I can (ab)use comprehensions to iterate arrays with Perl-like syntax by throwing away the result:
[alert(item) for each (item in [1, 2, 3])];

I can iterate object properties the same way:
var obj = { foo: 1, bar: 2, baz: 3 };
[alert(name + "=" + obj[name]) for (name in obj)];

Sweet!

16 comments:

Max Kanat-Alexander said...

The MDC docs for "for each" say not to iterate arrays that way, too, so I never use it on them.

-Max

Edward Lee said...

You can also use Iterators for the objects to get the key and val as an array:

[keyVal.join(" = ") for (keyVal in Iterator({a:1,b:2,c:3}))].join("\n")

output:
a = 1
b = 2
c = 3

Or alternatively, destructure the keyVal with [key,val] in Iterator(..)

Blake said...

Max, the reason for that is because for (each) in iterates up the prototype as well. So you run the risk of shooting yourself in the foot if you do:

Array.prototype.func = function() 42;
[i for each (i in [1,2,3])]

returns

1,2,3,function () 42

which is a risk you avoid if you explicitly iterate from i to length.

Myk said...

Edward: great tip! With an Iterator and destructuring assignment, the example in my post is:

[alert(key + "=" + val) for ([key, val] in Iterator({a:1,b:2,c:3}))]

Myk said...

Max, Blake: hmm, I use for each... in on arrays all the time. I guess I don't modify the Array prototype that often (or write code that runs in contexts in which other code modifies it).

Neil said...

Although .forEach avoids the prototype property trap, it has the disadvantage of creating a nested context so that "this" refers to the global scope.

Ancestor said...

Myk: One thing to remember is that for each is significantly slower than iterating over array indexes, last time I checked.

I like to use it but I avoid it in performance-sensitive places.

Edward Lee said...

> .forEach .. disadvantage of creating a nested context so that "this" refers to the global scope

You can pass in a second argument to forEach which will set "this" for the callback.

arr.forEach(func, this)

There are other ways to bind the callback function to a given "this" too.

shaver said...

With ES5 (coming soon to a SpiderMonkey trunk near you) the array-prototype-modification-leakage problem will be greatly reduced, since library authors will be able to make non-enumerable properties.

If this pattern becomes common we can probably also teach SpiderMonkey to just transform that into a loop, since the result is thrown away. (And for dense arrays, which we can detect in a number of ways, we can probably optimize further to avoid any iterator overhead, and just make it work like a for loop with a single fetch of the length. Good times!)

汇率查询 said...

It's so interesting to see these tricks.

boolean said...

Blake said...
> the reason for that is because for (each)
> in iterates up the prototype as well.

One can also avoid the so called "prototype trap" by using the hasOwnProperty method.
Array.prototype.func1 = function() {}
var z = [1,2,3];
for (var i in z) {
  if (z.hasOwnProperty(i)) {
    document.write(z[i] + ", ");
  }
}
prints 1, 2, 3,
does not print 1, 2, 3, function

Myk said...

boolean: very interesting! And since comprehensions can limit iteration to items that match a certain expression, you can actually use hasOwnProperty in a comprehension:

Array.prototype.foo = function() {};
var array = [1, 2, 3];
[alert(array[item]) for (item in array) if (array.hasOwnProperty(item))];
// alerts 1, 2, 3, not 1, 2, 3, function

Nevertheless, iterating Arrays as objects doesn't guarantee order, if I remember correctly, and thus is not recommended.

Anonymous said...

Shortest

[1,2,3].map(alert);

Myk said...

Anonymous: indeed, although that doesn't have the popular English-like order I like from Perl.

deepr voice said...

I guess I don't modify the Array prototype that often (or write code that runs in contexts in which other code modifies it).

RegCure Review said...

Each avoids the prototype property trap, it has the disadvantage of creating a nested context so that "this" refers to the global scope.