javascriptのfor-in文は、for-each文の代わりじゃない
例えばJAVAには、for-each文という便利な構文があります。
class Test { public static void main(String args[]) { int [] ary = new int[] { 1, 2, 3, 4, 5 }; for(int num : ary) { System.out.println(num + ","); } } }
実行結果
1,2,3,4,5,
これは拡張for文とも言われ、配列のすべての要素にアクセスすることができます。
Javascriptにも似たような構文のfor-in文があります。
が、このfor-in文実に曲者で、JAVAのfor-each文と同じ感覚で使うと、必ずどこかで痛い目を見ます。
例文を見てみましょう。
<html> <head> <title>for inテスト</title> </head> <body> <ul id="fruits"> <li>りんご</li> <li>みかん</li> <li>もも</li> </ul> <script type="text/javascript"> function test(){ var cnt = 0; var li = document.getElementsByTagName('li'); for (var i in li) { cnt++; } alert(cnt); } test(); </script> </body> </html>
HTML中の<li>要素の数を表示するコードです。*1
実行してみましょう。
え、5?!
<li>要素の数は3つですが、なぜか5が表示されます。何故だ。。。orz
for-in文とは
for-in文は配列の反復処理をする構文ではなく、オブジェクトの反復処理を行う処理です。
この場合、getElementsByTagName()の戻すDOM Elementオブジェクトのすべての要素にアクセスします。
オブジェクトの中身を見てみましょう。
全部で5つのプロパティがあります。
0〜2が<li>のElement、lengthはNodeListの長さ、__proto__はプロトタイプオブジェクトです。
for-in文は、これらの要素に順番にアクセスします。
そのため先ほどは期待値の3ではなく、プロパティ数の5が表示されたのです。
この仕様を理解しないでfor-inを使うと、予期せぬ結果が表示されて危険です。
forEachメソッド
他に手は無いんだろうか。
Foreach文 - Wikipedia によると、
ECMA-262第5版 (JavaScript 1.6, ActionScript 3.0) からは、ArrayインスタンスにforEachメソッドが追加された。
らしいので、for-in文のところを以下のように変えて実行してみます。
li.forEach(function(element){cnt++;});
実行結果
NodeListには使えませんでした。orz*2
>Uncaught TypeError: Object #<a NodeList> has no method 'forEach'
for each-in文
次!
同じくwikipediaによると。
ECMA-262第5版からは、オブジェクトの値で反復処理をするfor each-in文が導入された(スペースで分けられていることに注意)。
for-in文を以下のように変えて実行します。
for each (var i in li) {cnt++;}
実行結果
Chromeでは未実装?でした。orz
Uncaught SyntaxError: Unexpected identifier
自分で何とかしてみる
javascript 1.6の新しいArrayメソッド « ku によると、forEachの他にもfilter、every、map、someなど色々あるようですが、すべて対象はArrayインスタンスとなっており、NodeListには使えません。
仕方が無いので、prototypeを拡張して自力で実装してみます。
Object.prototype.each = function(func){ for (var i = 0; i < this.length; i++){ func(this[i]); } }
使い方
li.each(function(node){ cnt++ });
これで結果は3になります。ヤッター。
。。。が、この方法は下策でしょう。
すべてのObjectが汚染され、for-inで列挙されるようになってしまいます。
prototype汚染は非常にデリケートな問題です。
結局
素直にfor文を使いましょう。
for (var i = 0; i < li.length; i++){ alert(li[i].innerHTML); }
はい解決。
for-in文がこの先生きのこるには
そんな配列やオブジェクトには使えないfor-in文ですが、そんな彼にも使い道があります。
連想配列です。
連想配列は添え字が数字ではなく文字の配列*3でlengthプロパティを持たないため、普通のfor文は使えません。
そういう場合はfor-in文を使います。
var test = {"hoge":"fuga","foo":"bar"}; for (var i in test){ alert(test[i]); }
参考
Foreach文 - Wikipedia
JavaScript の配列と連想配列の違い - IT戦記
javascript 1.6の新しいArrayメソッド « ku