【STEP.14】
for・for…in・for…of命令の使い方をマスターしよう!

指定回数だけループを処理する【 for命令 】

for命令とは

条件式の真偽に応じてループを制御するwhiledo…while命令に対して、指定された回数だけ繰り返し処理を行うのがfor命令です。

for命令

for (initial; condition; expression) {
  ...statements...
}
initial      初期化式
condition    ループ継続条件式
expression   増減式
statements   繰り返すべき処理

同じ処理内容をdo…while命令で記述した場合と、for命令で記述した場合の比較をしてみましょう。

まずはdo…while命令で記述した場合です。

let x = 8;

do {
  console.log('xの値は' + x);
  x++;
} while (x < 10);
//結果:「xの値は8」「xの値は9」

次は、for命令で記述した場合です。

for (let x = 8; x < 10; x++) {
  console.log('xの値は' + x);
}
//結果:「xの値は8」「xの値は9」

変数の値によってループを制御する場合には、whiledo...while命令よりもfor命令の方がコンパクトにコードを記述できることがおわかりいただけると思います。

for命令は先頭の「初期化式」「ループ継続条件式」「増減式」の3つの式でループを制御します。

まず、forブロックに入った最初のループで、初期化式を1回だけ実行します。(上のサンプルコードでは「let x = 8」)一般的には、この初期化式でカウンター変数ループ変数)を初期化します。カウンター変数とは、for命令によるループの回数を管理する変数のことです。

次のループ継続条件式は、ブロック内の処理を継続するための条件式を表します。上のサンプルコードでは「x < 10」なので、カウンタ変数xが10未満である間だけ、ループを繰り返します。

そして最後に、増減式は、ブロック内の処理が1回実行される度に実行されます。通常、カウンター変数を増減するインクリメント/デクリメント演算子、または代入演算子を指定します。ここでは「x++」としているので、ループ都度、カウンター変数xに1を加算します。もちろん、「x += 2」としてカウンター変数を2ずつ加算することもできますし、「x--」として1ずつ減算していくこともできます。

for命令の動作

これらの式には任意の式を指定できます。

式の組み合わせによっては無限ループの原因にもなるので、注意が必要です。

たとえば、次のようなforループは、カウンター変数xの初期値が0で、その後、ループの都度にデクリメント(減算)されていくので、ループ継続条件である「x < 5」 がfalseになることは永遠にありません。

for (let x = 0; x < 5; x--) {...}

次のコードは初期化式/ループ継続条件式/増減式がすべて省略されたパターンです。

for(;;){...}

この場合、for命令は無条件にループを継続します。

カンマ演算子を利用した例

カンマ演算子を利用することで、初期化式、ループ継続条件式、増減式に複数の式を指定することもできます。

カンマで区切られた式は、先頭から順に実行されます

たとえば、以下の例では初期化式で変数ijをそれぞれ1に初期化し、増減式で双方ともインクリメントしています。

for (let i = 1, j = 1; i < 5; i++, j++) {
  console.log('i*Jは' + i * j);
}
i*Jは1
i*Jは4
i*Jは9
i*Jは16

好みにもよりますが、ブロック内の処理がごく単純な場合には、カンマ演算子を用いることで、コードをシンプルに表現できます。(ただし、乱用すべきではありません。)

連想配列の要素を順に処理する【for...in命令】

for...in命令とは

forwhiledo...while命令とはやや毛色が異なる繰り返し命令が、for...in命令です。

for...in命令は、指定された連想配列(オブジェクト)の要素を取り出して、先頭から処理します。

for...in命令

for(variable in object) {
  ...statements...
}
variable    仮変数
object      オブジェクト
statements  ループで実行する命令

仮変数は、連想配列(オブジェクト)のキーを一時的に格納するための変数です。

for...inブロックでは、この仮変数を介して、個々の要素にアクセスします。

ここで、仮変数に格納されるのが、要素値そのものではないことに注意が必要です。

for...in命令の動作

for...in命令の基本

たとえば以下は、連想配列から要素値を順に表示する例です。

let data = { apple:150, orange:100, banana:120 };
for (let key in data) {
  console.log(key + '=' + data[key]);
}
apple=150
orange=100
banana=120

配列ではfor...in命令を利用してはいけません

構文上、配列でもfor...in命令を利用することは可能です。

たとえば、以下のコードを確認してみましょう。

let data = ['apple','orange','banana'];
for (let key in data) {
  console.log(data[key]);
}
//結果:「apple」「orange」「banana」を順に出力

配列の内容が順番に出力され、一見して、正しく動作しているように見えます。

しかし、以下のようなコードではどうでしょう。

let data = ['apple','orange','banana'];
//配列オブジェクトにhogeメソッドを追加
Array.prototype.hoge = function(){}
for (let key in data) {
  console.log(data[key]);
}
//結果:「apple」「orange」「banana」「function hoge()」を順に出力

この場合、プロトタイプで拡張されたメンバーまでが列挙されてしまうのです。

また、以下のような問題もあります。

  • for...in命令では処理の順序も保証されない
  • 仮変数にはインデックス番号が格納されるだけなので、コードがあまりシンプルにならない(=値そのものではないので、かえって誤解を招く)

このようなな理由から、for...in命令は連想配列(オブジェクト)を操作するにとどめ、配列の列挙には、for命令、もしくはfor...of命令を利用すべきです。

配列などを順に処理する【for...of命令】

for...of命令とは

配列などを順番に列挙するためのもう1つの手段、それがES2015で追加されたfor...of命令です。

「配列など」というのは、正しくはfor...of命令では配列だけでなく、Arrayライクなオブジェクト(NodeListargumentsオブジェクトなど)、イテレーター/ジェネレーターなども処理できるためです。

これらを総称して列挙可能なオブジェクトなどとも呼びます。

for...of命令

for(variable of iterable) {
  ...statements...
}
variable    仮変数
iterable    列挙可能なオブジェクト
statements  ループで実行する命令

for...of命令の基本

構文はfor...in命令とほぼ同じなので、さっそく、具体的な例を見てみましょう。

let data = ['JavaScript','CoffeeScript','TypeScript'];
for (let value of data) {
  console.log(value);
}
JavaScript
CoffeeScript
TypeScript

確かに、配列dataの内容が正しく出力されていることが確認できます。

また、for...in命令では、仮変数にキー名(インデックス番号)が渡されていたのに対して、for...of命令では値を列挙している点にも注目です。

ループを途中でスキップ・中断する【break・continue命令】

break命令とは

通常、whiledo...whileforfor...infor...of命令は、あらかじめ決められた終了条件を満たしたタイミングでループを終了しますが、「特定の条件を満たした場合に、ループを強制的に中断したい」というケースもあります。

そのようなケースで利用できるのが、break命令です。

たとえば以下は、配列dataの内容を順番に出力する例です。

ただし、途中で空文字列を見つけたところで、ループを終了します。

let data = ['計量','こねる','まとめる','','ガス抜き','成形','焼く'];
	
for (let i = 0; i < data.length; i++) {
  if (data[i] === '') { break; }
  console.log(data[i]);
}
//結果:「計量」「こねる」「まとめる」を順に出力

このように、break命令は、if命令のような条件分岐命令と併せて使用するのが一般的です。

もう一つbreak命令を使用した例を見てみましょう。

以下は、変数iを1~100の間で加算していき、合計値(変数result)が1000を超えたところでループを脱出する例です。

let result = 0;
for (var i = 1; i < 100; i++) {
  result += i;
  if (result > 1000) { break; }
}
console.log('合計値が1000を超えるのは' + i);
//結果:合計値が1000を超えるのは45

break命令

continue命令とは

一方、ループを完全に抜けてしまうのではなく、現在のループだけをスキップするならばcontinue命令を利用します。

たとえば以下は、配列dataの内容を順に出力する例です。

ただし、空文字列はスキップします。

let data = ['計量','こねる','まとめる','','ガス抜き','成形','焼く'];
	
for (let i = 0; i < data.length; i++) {
  if (data[i] === '') { continue; }
  console.log(data[i]);
}
//結果:「計量」「こねる」「まとめる」「ガス抜き」「成形」「焼く」を順に出力

もう一つcontinue命令を使用した例を見てみましょう。

以下は、変数iを1~100の間で奇数のみ加算し、その合計値を求めるためのサンプルです。

let result = 0;
for (let i = 1; i < 100; i++) {
  if (i % 2 === 0) { continue };
  result += i;
}
console.log('合計:' + result);
//結果:2500

ここではカウンター変数iが偶数(=変数iが2で割り切れる)の場合にのみ処理をスキップすることで、奇数だけの合計値を求めています。

continue命令

複数の階層をまとめて脱出/スキップする【ラベル構文】

ネストされたループの中でbreakcontinue命令を使用した場合、デフォルトでは最も内側のループを脱出/スキップします。

以下のサンプルを見てみましょう。

<div id ="table"></div>
let table = document.getElementById('table');
let result = '';
for (let i = 1; i < 10; i++) {
  for (let j = 1; j < 10; j++) {
    let k = i * j;
    if (k > 30) { break; }
    result += (k + '&nbsp');
  }
  result += '<br />';
}
table.innerHTML = result;

ここでは、変数k(カウンター変数ijの積)が30を超えたところで、break命令を実行しています。

これによって、内側のループ(太字部分)を脱出するので、最終的に「積が30以下の値だけを表示する九九表」が生成されることもなります。

これを、「一度、積が30を超えたら、九九表の出力そのものを停止したい」とする場合には、次のようなコードを記述します。

<div id ="table"></div>
let table = document.getElementById('table');
let result = '';
kuku:
for (let i = 1; i < 10; i++) {
  for (let j = 1; j < 10; j++) {
    let k = i * j;
    if (k > 30) { break kuku; }
    result += (k + '&nbsp');
  }
  result += '<br />';
}
table.innerHTML = result;

このように、脱出先のループの先頭にラベルを指定するわけです。

ラベルは以下の形式で指定します。

ラベル名:

ラベルには、任意の名前を付けることができます。

あとは、breakcontinue命令の側でも、

break ラベル名;

のようにラベルを指定することで、今度は(内側のループではなく)ラベルのついたループを脱出できます。

もう一つ具体例を見てみましょう。

次のサンプルは二次元配列の内容を出力する例です。途中で「からあげ」を含むキーワードが見つかったところで、ループを完全に脱出します。

let data = [
  ['緑茶','コーヒー'],
  ['おにぎり','サンドイッチ'],
  ['肉じゃが','からあげ'],
  ['まんじゅう','ケーキ']
];
	
nest:
for (let i = 0; i < data.length; i++) {
  for (let j = 0; j < data[i].length; j++) {
    if (data[i][j] === 'からあげ') { break nest; };
    console.log(data[i][j]);
  }
  console.log('---------------------');
}
緑茶
コーヒ
---------------------
おにぎり
サンドイッチ
---------------------
肉じゃが

イテレーターの仕組みを理解しよう

イテレーターとは、自分自身の中身を列挙するための仕組みを備えたオブジェクトのことです。

配列(Array)をはじめ、SetMapStringなどのオブジェクトは、いずれも内部的にはイテレーターを備えているので、for...of命令で配下の要素を列挙できていたわけです。(このようなオブジェクトのことを列挙可能なオブジェクトと呼びます。)

イテレーターの存在を意識するために、原始的なコードで表現したのが以下です。(本来、このようなコードを書く意味はないので、あくまでもイテレーターの仕組みを理解するためだけの例です。)

let data = ['JavaScript','CoffeeScript','TypeScript'];
let itr = data[Symbol.iterator]();
let d;
while (d = itr.next()) {
  if (d.done) { break; }
  console.log(d.done);
  //結果:false,false,false
  console.log(d.value);
  //結果:JavaScript,CoffeeScript,TypeScript
}

[Symbol.iterator]メソッドは、配列が保持するイテレーター(iteratorオブジェクト)を返します。(let itr = data[Symbol.iterator]();

イテレーターは、配列の次の要素を取得するためのnextメソッドを持ちます。(while (d = itr.next(){...}

ただし、nextメソッドの戻り値は要素値そのものではなく、次に掲げる表のようなプロパティを備えたオブジェクトです。

【nextメソッドが返すオブジェクトのメンバー】
プロパティ概要
doneイテレーターの末尾に到達したか
value要素の値

この例であれば、doneプロパティがtrueを返したところでループを抜けることで、配列の内容をすべて走査しているわけです。

このように、for...of命令とは「イテレーターを取得し、doneプロパティで末尾を判定しながら、valueプロパティで値を取り出す」という操作をまとめて賄ってくれるシンタックスシュガーというわけです。

MEMO
ー[Symbol.iterator]の意味ー
Symbol.iteratorはオブジェクト既定のイテレーターを特定するためのシンボルです。

これを[...]でくくっているのはComputed property namesの構文で、Symbol.iteratorが表すシンボルをキーに、配列(Array)オブジェクトのメンバーにアクセスしなさい、という意味です。

いわゆる「data.Symbol.iterator」ではないので、混同しないように注意が必要です。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です