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:

Blogger 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

8:20 PM  
Anonymous 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(..)

9:05 PM  
Blogger 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.

9:46 PM  
Blogger 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}))]

12:29 AM  
Blogger 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).

12:31 AM  
Blogger 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.

1:29 AM  
Anonymous 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.

4:56 AM  
Anonymous 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.

9:59 AM  
Blogger 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!)

5:40 AM  
Anonymous 汇率查询 said...

It's so interesting to see these tricks.

3:34 AM  
Blogger 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

1:17 PM  
Blogger 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.

2:01 PM  
Anonymous Anonymous said...

Shortest

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

2:37 PM  
Blogger Myk said...

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

2:43 PM  
Anonymous 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).

3:13 AM  
Anonymous 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.

2:48 AM  

Post a Comment

Links to this post:

Create a Link

<< Home