JavaScriptのループ文まとめ

お蔵出しシリーズ2。2008年2月に書いたらしいものに加筆したら原型がなくなりました。

ループ文とは

通常は上から下に処理が進んでいくプログラムを、一定条件に当てはまるあいだだけ繰り返し処理する制御文。

for文

初期値、条件、増減式を設定して、条件が真であるあいだ、一定の処理を繰り返す命令。
正確に言うと条件が偽になったら処理をしないでループを抜ける、ってことになる。


ループ用の変数に初期値(数値)を代入して、それを増減してループ回数を制御する。なので、主にループ回数が開始前に設定できる場合に使う。

for( 式1 ; 式2 ; 式3 ) {
  繰り返したい処理
}
式1
初期値を設定する式。ループ開始前に一度だけ評価される。
式2
条件式。毎回のループの最初に評価され、もしも偽(false)だったら{}内の処理を行わずにループを抜ける。
式3
ループ用の変数の値を増減する式。後処理。毎回のループの最後に評価される。式2が偽になった場合はその時点でループを抜けてしまうので、この式は実行されない。


ちなみに、式1と式3には複数の式を書くことができる。その場合は式をカンマ , で区切ること。*1
例えば「配列arrの要素を順番にすべて書き出す」みたいな処理をしたい場合、

var arr = [10, 20, 30, 40];
for( var i = 0 ; i < arr.length ; i++ ) {
  alert( arr[i] );
}
// 結果:「10」「20」「30」「40」

って書いてもいいんだけど、これだと式2にあたる「 i < arr.length 」の評価時にいちいち配列の要素数を調べることになる。

var arr = [10, 20, 30, 40];
for( var i = 0 , arr_len = arr.length ; i < arr_len ; i++ ) {
  alert( arr[i] );
}
// 結果:「10」「20」「30」「40」

こうすれば、要素数を調べる処理はループ開始前の一回だけで済む。ループ回数が最初に固定できる場合は、採用すると処理が早くなるかも。

while 文

条件を設定し、それが真である間、一定の処理を繰り返す命令。

while( 条件式 ) {
  繰り返したい処理
}

初期値の設定や後処理がないので、ループ内で条件を変更するか、break(後述)とかでループを抜ける必要がある。でないと無限ループになる。

while(true) {
  breakがなければ無限ループされる処理
}


forのところで書いた「配列arrの要素を順番にすべて書き出す」処理をwhile文でやる場合、

var arr = [10, 20, 30, 40];
var i = 0, arr_len = arr.length;
while( i < arr_len ) {
  alert( arr[i] );
  i++;
}
// 結果:「10」「20」「30」「40」

って書くと、for文とまったく同じことができる。


もうちょっと抽象化すると、

for( 式1 ; 式2 ; 式3 ) {
  繰り返したい処理
}

式1;
while( 式2 ) {
  繰り返したい処理
  式3;
}

が同じということ。
でもwhileでこれをやるくらいなら素直にfor使えばいいと思う。


whileの場合、たとえばarrを破壊してしまってもいいなら以下のようなことができる。

var arr = [10, 20, 30, 40];
while( arr.length ) {
  alert( arr.shift() );
}
// 結果:「10」「20」「30」「40」

Array.shift()は配列から最初の要素を取り除き、その要素を返す。
ループのたびに配列arrから要素がひとつずつ減っていって、要素がなくなったら次のループ開始時には条件式「 arr.length 」が0(真偽値に変換するとfalse)になるので、ループを抜ける。
複数行にわたる文字列を取得して、一行ずつ最後まで処理したい場合なんかに使える。

do-while 文

while文と似てるけど、これは一度は必ず一定の処理を行い、その後、条件が真のあいだだけ同じ処理を繰り返す場合に使う命令。

do {
  一度は必ず行うけど、もしかしたら繰り返したいかもしれない処理
} while( 条件式 )

for文やwhile文の場合、条件式が最初からfalseだったらその時点でループを抜けてしまうので、{}内の処理は一度も行われない。
do-while文では、条件式は{}内の処理が行われた後に評価される。なので条件式の結果がどうであっても、とりあえず一度は必ず処理が行われる。

つっても、今までこれ使う機会は一度もなかったんだが……

break文、continue文

ループの途中で処理の流れを中断したい場合に使う命令文。
breakの場合は処理を中断し、直ちにループを抜け、次の処理に移る。

for( var i = 0 ; i < 10 ; i++ ) {
  if( i==5 ) {
    break; // forループを脱出
    alert( "ここは処理されない" );
  }
  alert( "i = " + i );
}
alert( "ループ外 i = " + i );
// 結果:「i = 0」「i = 1」「i = 2」「i = 3」「i = 4」「ループ外 i = 5」

breakでループを抜けた場合、ループ終了時の処理(「i++」の部分)は行われていない。

continueの場合は、直ちにその回のループ終了処理を行う

for( var i=0 ; i<10 ; i++ ) {
 if( i==5 ) {
    continue; // ループ1回分をここで終了する
    alert( "ここは処理されない" );
  }
  alert ( "i = " + i );
}
alert( "ループ外 i = " + i );
// 結果:「i = 0」「i = 1」「i = 2」「i = 3」「i = 4」「i = 6」「i = 7」「i = 8」「i = 9」「ループ外 i = 10」

while文の場合は、条件の変更前にcontinueを置いてしまうと無限ループになるので注意。

for-in文

for-in文はfor文から派生した構文。
配列の各要素……というか、オブジェクトの各プロパティにアクセスして、同じ処理を繰り返す場合に使う。

var arr = [10, 20, 30, 40];
for( var i in arr ) {
  alert( arr[i] );
}
// 結果:「10」「20」「30」「40」すべての要素を列挙

ちなみに i にはオブジェクトのプロパティ名(文字列)が入る。

var arr = [10, 20, 30, 40];
for( var i in arr ) {
  alert( i + " : " + typeof i );
}
// 結果:「0 : string」「1 : string」「2 : string」「3 : string」

数値に見えるけど文字列。


例は配列で書いたけど、実はfor-inは配列に使わないほうがいい。
以下は理由。わからなければ「使っちゃいけないのねフーン」でいいと思う(よくないけど)。


for-in構文は「オブジェクトのプロパティ」にアクセスするので、例えば他のところでArrayオブジェクトが拡張された場合、そのプロパティまで列挙してしまう。

Array.prototype.hoge = function(){ alert("ほげー"); }; // Arrayオブジェクトを拡張
var arr = [10, 20, 30, 40];

for( var i in arr ) {
  alert( arr[i] );
}
// 結果:「10」「20」「30」「40」「function(){ alert("ほげー"); }」最後のはhogeの中身が見えてる

オブジェクト(インスタンス)自身が持っているプロパティだけを列挙したいときは、都度hasOwnProperty()で判定すればOK。

Array.prototype.hoge = function(){ alert("ほげー"); }; // Arrayオブジェクトを拡張
var arr = [10, 20, 30, 40];

for( var i in arr ) {
  if(arr.hasOwnProperty(i)){
    alert( arr[i] );
  }
}
// 結果:「10」「20」「30」「40」hogeはarrが持っているプロパティじゃないのではじかれる
var arr = [10, 20, 30, 40];
arr.hoge = function(){ alert("ほげー"); }; // arrに対してhogeメソッドを追加

for( var i in arr ) {
  if(arr.hasOwnProperty(i)){
    alert( arr[i] );
  }
}
// 結果:「10」「20」「30」「40」「function(){ alert("ほげー"); }」hogeも列挙される

for-inのループはプロパティを定義した順番で行われる(正確には、プロパティに値を代入した順番)。
また、プロパティの値が定義されていないものは無視される。

var obj = new Object;
obj.age;               // 宣言だけはしたけど未定義(undefined)
obj.name = "権兵衛";
obj.age  = "20";       // ここで値を代入
obj.addr = "ちば";
obj.hobby;             // 宣言だけはしたけど未定義(undefined)
for( var i in obj ) {
  alert( i + " : " + obj[i]);
}
// 結果:「name : 権兵衛」「age : 20」「addr : ちば」hobbyは出てこない


疲れたのでこの辺で切り上げ。

*1:正確に言うと、式1〜式3のそれぞれが式であればいいので、複数の式をカンマ演算子でつないで一つの式にしているというだけ。式2も同じことができるけど、評価後の値によって分岐するので順番が大事になる。ループ毎に処理したいものは素直に式3に書いたほうがいい。