JavaScript

ループ文

 条件文を理解しやすくするために、JavaScriptのインタプリタがソースコード中の分岐路をたどる、という風にたとえました。これに対して、ループ文は、道を戻ってソースコード中のある部分を繰り返すということになります。JavaScriptには 4つのループ文があります。while文、do/while文、for文、for/in文の 4つです。これから、項を分けてそれぞれの文について説明していきます。ループ文がよく使われるのは、配列の要素を巡回する時です。

while 文

 JavaScriptが条件判定するための基本的な制御文が ifであるとすれば、JavaScriptが繰り返し処理をするための基本的な制御文が while文の書式は次のとおりです。


while (expression)
	statement

 while文を実行すると、JavaScriptインタプリタは、まず expression を評価します。その結果が false と評価される場合は、インタプリタはループ本体の文を飛ばし、次の文の処理へ進みます。評価結果が true と評価される場合は、statement を実行し、それから元の expression の評価に戻ります。つまり、式が true と評価される間は、インタプリタは statement を繰り返し実行します。なお、while(true) とすると、無限ループになることに注意しましょう。

 一般的に、全く同じ処理を何度も JavaScriptにさせる人はいないと思います。そのような無駄なことをさせないために、ループを「繰り返す」たびに変化する変数を利用します。変数の値が変化すれば、実行する内容も毎回変わります。この変数が expression に含まれていれば、ループを繰り返すたびに expression の値も変わります。これは非常に重要なことです。もし expression が true と評価される値で始まり、そのまま何も変わらなければ、ループも終わらなくなってしまいます。次の例を見てください。この例では 0 から 9 までの数値を出力します。


var count = 0;
while (count < 10) {
	console.log(count);
	count++;
}

 このコードでは、変数 count の値は 0 から始まります。ループの本体を実行するたびに、count の値が 1つ増えます。ですから、ループを 10回繰り返すと、count の値は 10になるので、while 文の式の評価結果は false になります。その時点で while 文の処理は終了し、JavaScript インタプリタはプログラムの次の文へと処理を進めます。このようにほとんどのループでは、count のようなカウンタ変数を使用します。ループカウンタの変数にはどのような名前を付けてもかまいませんが、一般には i、j、k がよく使われます。ただし、プログラムをわかりやすくするためにも、もう少し意味のある名前を使うことをお勧めします。

do/while 文

 do/whileループは whileループとよく似ていますが、expression がループの先頭ではなく、最後にある点が異なります。つまり、do/while文の場合、ループの本体が少なくとも 1回は常に実行されます。do/while文の書式は次のとおりです。


do
	statement
while (expression);

 do/whileループが whileループほど使われないのは、少なくとも 1回実行するループを必要とする状況があまり多くないからです。次の例を見てください。


function printArray(a) {
	var len = a.length, i = 0;
	if (len == 0)
		console.log("Empty Array");
	else {
		do {
			console.log(a[i]);
		} while (++i < len);
	}
}

 do/whileループと通常の whileループには相違点がいくつかあります。まず、doループは、ループの開始を示すキーワード doと、ループの終了を示しループ条件を記述するキーワード whileが必要です。また、whileループとは異なり、doループの最後にセミコロン( ; )を書かなければなりません。whileループでは、ループ本体を中括弧( {} )で囲んだ場合は、セミコロンは必要ありません。

for 文

 for文もループです。たいていの場合、while文より便利です。先ほどの whileループの例もそうですが、一般的に、ループには共通となるパターンがあります。そのパターンとは次のとおりです。ループには何らかのカウンタ変数があり、ループを開始する前にこの変数を初期化します。次に、この変数をテストしてから、ループの繰り返し処理を行います。最後に、ループ本体の末尾でカウンタ変数を更新してから、再び変数をテストします。for文を使えば、このようなパターンのループを簡単に記述できます。for文では、初期化、テスト、更新、というループ変数の重要な要素を、明示的にまとめて記述します。for文の書式は次のとおりです。


for(initialize ; test ; increment)
	statement

 initialize、test、incrementは、それぞれ初期化、テスト、更新を行う式です。これらの 3つの式はセミコロンで区切ります。for文では、この 3つの処理をまとめて、ループの先頭に明示的に記述します。このやり方なら、ループで何をしているのかがよくわかり、ループ変数の初期化や更新を忘れるなどの誤りを防げます。for文の働きは、for文と同じ処理を行う次の while文と比較するとよくわかると思います。


initialize;
while(test) {
	statement
	increment;
}

 ループを開始する前に、initialize式を 1回だけ評価します。通常、initialize式は代入などの副作用を伴います。JavaScriptでは、var文を使って変数を宣言することもできます。ループカウンタ変数の宣言と初期化が同時にできて便利です。繰り返しの前に test を実行し、その結果に基づいてループ本体を実行するかどうかを決めます。test の評価結果が true と評価される場合は、ループ本体の statement を実行します。最後に、increment式を評価します。increment式も副作用を伴うものでなければなりません。一般に、increment式は代入式か、あるいはインクリメント演算子( ++ )またはデクリメント演算子( -- )を使った式になります。

 先ほど whileループを使って 0 から 9 を出力するコードを紹介しました。同じコードを、for文を使って書き直したものを紹介します。違いを確認してください。


for(var count = 0; count < 10; count++)
	console.log(count);

 もちろん、ここで紹介した簡単なループよりもっと複雑なループもたくさんあります。このような複雑なループでは、ループの繰り返し処理で変化する変数が複数個ある場合もあります。そこでカンマ演算子( , )が登場します。カンマ演算子が使われるのは、ほぼこの場合だけです。カンマ演算子を使うと、initialize や increment などで複数の式を 1つの式にまとめ、forループで複数の式を記述できるようになります。実例を 1つ挙げておきます。


var i,j;
for(i = 0, j = 10 ; i < 10 ; i++, j--)
	sum += i * j;

 ここまで紹介したループの例では、ループ変数は全て数値でした。ループ変数として数値が使われることは非常に多いのですが、数値でなければならないわけではありません。例えば、次に紹介するコードでは、ループを使ってリンクリスト型のデータ構造をたどって、リストの最後のオブジェクト(つまり、nextプロパティを持たない最初のオブジェクト)を返します。


function tail(o) {
// リンクリスト o の末尾を返す。
	for(; o.next; o = o.next) /* 空文 */ ;
	// o.next が true と評価される間たどる。
	return o;
}

 このコードには initialize式がありません。3つの式はどれも省略して構いません。ただし、式を省略しても 2つのセミコロンは必ず記述します。test式を省略した場合は、ループは永久に繰り返されます。for(;;) と記述すると、while(true) と同じように、無限ループになります。

for/in 文

 for/in文では forキーワードを使います。しかし、for/in文は、通常の forループとは全く異なるループ処理を行います。書式は次のとおりです。


for (variable in object)
	statement

 variable には変数を記述するのが普通ですが、左辺値(「演算子の概要」を参照)と評価される式や、変数を 1つだけ宣言する var文も記述できます。つまり、代入式の左側として適切なものを記述します。object は、評価するとオブジェクトになる式です。statement は、ループの本体を形成する文または文ブロックです。

 通常のループ文は、配列の要素を巡回するときに便利です。例を見てください。


for(var i = 0; i < a.length; i++)
// 変数 i に配列のインデックスを代入する。
	console.log(a[i]);
	// 配列の各要素の値を出力する。

 これに対して、for/inループ文は、オブジェクトのプロパティを巡回するときに便利です。こちらも例を見てください。


for(var p in o)
// o のプロパティ名を変数 p に代入する。
	console.log(o[p]);
	// 各プロパティの値を出力する。

 for/in文を実行すると、JavaScriptインタプリタは、まず object式を評価します。評価結果が null や undefined の場合、インタプリタはループ文をスキップし、ループ文の次の文に移動します。object式の値が基本型の場合、その値をラッパーオブジェクト(「ラッパーオブジェクト」を参照)に変換します。基本型でなければ、object式の値はすでにオブジェクトです。次に、インタプリタは、オブジェクトの列挙可能なプロパティごとに、ループ本体を一度ずつ実行します。ただし、ループ本体を実行する前に、インタプリタは毎回 variable式を評価し、プロパティの名前(文字列値)を代入します。

 for/inループの variable には、評価すると代入式の左側としてふさわしい値になる式であれば、どんな式でも記述できます。ループを実行するたびに、この式は評価されます。つまり、ループを実行するたびに違う値にすることも可能です。例えば、次のようなコードにより、オブジェクトのプロパティの名前をすべて配列にコピーできます。


var o = {x:1, y:2, z:3};
var a = [], i = 0;
for(a[i++] in o) /* 空文 */;

 JavaScriptでは、配列はオブジェクトの一種です。したがって、for/inループでは、オブジェクトのプロパティだけでなく、配列のインデックスも変数に代入されます。例えば、先ほどの例の後に次のコードを記述すると、配列のインデックスである 0、1、2 が出力されます。


for(i in a) console.log(i);

 実際には、for/inループ文で、オブジェクトのすべてのプロパティが調べられるわけではありません。列挙可能なプロパティだけが調べられます。コア JavaScript言語で定義されている様々な組み込みメソッドは調べられません。例えば、すべてのオブジェクトは toString() メソッドを持ちますが、for/inループでは toStringプロパティは調べられません。組み込みメソッド以外にも、組み込みオブジェクトの多くのプロパティも調べられません。ただし、コード中で定義されたプロパティやメソッドについては全て調べられます(このようなプロパティやメソッドについても、ECMAScript5では、「プロパティ属性」で説明予定の方法を使って調べられないようにすることもできます)。ユーザー定義の継承プロパティも、for/inループで調べられます。

 for/inループのループ本体でまだ変数に代入されていないプロパティを削除した場合、そのプロパティは変数に代入されません。ループ本体で新たにプロパティを定義した場合、そのプロパティが変数に代入されないのが普通です(ただし、処理系によってはループの後に追加された継承プロパティが代入される場合もあります)。

プロパティの列挙順序

 ECMAScript仕様では、for/inループでオブジェクトのプロパティが代入されていく順序については規定していません。しかし、実際には、メジャーなブラウザ上のほぼすべての JavaScript処理系では、オブジェクトのプロパティが定義された順で、プロパティを調べられるようにしています。つまり、まず古いプロパティから調べることになります。オブジェクトがオブジェクトリテラル形式で作成されたのであれば、プロパティを調べる順序は、リテラル中で記述した順序になります。Webサイトやライブラリによっては、この順序に依存したものがあります。このような理由から、ブラウザベンダーは、おそらく今後も、この動作を変更することはないでしょう。

 ここまでのところで説明したのは、「単純な」オブジェクトでのプロパティの列挙順序についてです。この順序については、ブラウザ間でも互換性が高いものになっています。しかし、以下のような場合には、順序は処理系依存となり、可搬性は低くなります。

 継承プロパティは、オブジェクト自身のプロパティ(継承されたものではないプロパティ)の後に、変数に代入されていくのが一般的です。ただし、あくまで一般的ということであり、すべての処理系がこのような処理を行うわけではありません。オブジェクトが 2つ以上の「プロトタイプ」からプロパティを継承する場合(「プロトタイプチェーン」にオブジェクトが複数ある場合)、プロトタイプチェーン中のプロトタイプオブジェクトごとに作成順でプロパティが代入されていきます。1つのプロトタイプオブジェクトのプロパティをすべて調べたら、その次のプロトタイプオブジェクトに移ります。処理系によっては、配列のプロパティを作成順ではなく、数値順で調べられるようにしているものもあります。これもすべての処理系がそうだというわけではありません。また、このような処理系でも、配列の中に数値以外のプロパティが含まれていたり、配列が疎であったり(配列のインデックスが飛んでいたり)すると、作成順に動作を変更します。