JavaScript/制御構造

出典: フリー教科書『ウィキブックス(Wikibooks)』
JavaScript with文 から転送)
ナビゲーションに移動 検索に移動

制御構造( control flow )とは、「順次」「分岐」「反復」という基本的な実行順序の制御のことを言います。

分岐[編集]

次のプログラムは、、

  • nが0より小さいときには「負の数」
  • nが0より大きいときには「正の数」
  • nが0のときには「零」
  • いづれにもあてはまらないなら、n自身を表示します。
"use strict";

const n = 0 / 0;
if (n < 0) {
  console.log("負の数");
} else if (n > 0) {
  console.log("正の数");
} else if (n > 0) {
  console.log("零");
} else {
  console.log(n);
}
実行結果
NaN
0 / 0は、除数がゼロであると考えれば無限大ですが、被除数がゼロと考えるとゼロというパラドックス持ちで、ISO/IEC/IEEE 60559:2011 ではこのケースを NaN 非数とします。

この様な、分岐を始めとする制御構造はプログラミングを行う上で欠かせません。

if-else[編集]

if文( if statement )は「もし〜ならば」を表す条件分岐構文です。 if文では()の中に書かれた条件が truthy であるとき続く文が実行されます。 次のプログラムは、n < 0(nが0より小さい)という条件が真であるときのみ「負の数」と表示します。

"use strict";

const n = -1;
if (n < 0) {
  console.log("負の数");
}
実行結果
負の数

if文のあとにelse節( else clause )を置くと、else節の文はif文の条件が falsy であるときのみ実行されます。 次のプログラムは、n < 0(nが0より小さい)という条件が真であれば「負の数」、さもなくば「自然数」と表示します[1]

"use strict";

const n = 0;
if (n < 0) {
  console.log("負の数");
} else {
  console.log("自然数");
}
実行結果
自然数

if文とelse節をあわせてif/else文と呼びます。

else節の文をif文とすることで、次のように何個もつらねることができます。

"use strict";

const n = 0;
if (n < 0) {
  console.log("負の数");
} else if (n > 0) {
  console.log("正の数");
} else if (n == 0) {
  console.log("零");
} else {
  console.log(n);
}
実行結果
このプログラムはn < 0ならば「負の数」、そうでなくn > 0ならば「正の数」、そうでなくn == 0ならば「零」、そうでもないならば n の値が表示されます。
else ifという部分に注目してください。
何通りもの条件で処理を分岐したい場合は、このelse ifを何個も増やしていくことになります。
if文やelse節の文はブロックである必要はありません。
単文(1つの文)しか入っていない場合は、次のように書いても同じことです。
"use strict";

const n = 0;
if (n < 0) console.log("負の数");
else if (n > 0) console.log("正の数");
else if (n == 0) console.log("零");
else console.log(n);

なお、この場合は条件演算子を用いて簡潔に書くことができます。

"use strict";

const n = 0;
console.log(n < 0  ? "負の数"
          : n > 0  ? "正の数"
          : n == 0 ? "零" 
          :          n);

if文の条件式はすべてtrueかfalseの真偽値として評価されます。たとえば、数値の0は真偽値に変換するとfalseになるので[2]、次のif文のブロックは絶対に実行されません(デッドコード)。

if (0) {
    // たどり着くことはない
}
0以外にもfailyが
他の言語(例えばC言語)では、nが0に等しいかどうかは
if (n == 0) { /* ... */ }

のほかに

if (!n) { /* ... */ }

と書けます、同様に nが0に等しくないかどうかは

if (n != 0) { /* ... */ }
のほかに
if (n) { /* ... */ }

とも書けます。

しかし、JavaScript では「0以外にも falsy な値がある」(例えば !"" は真になります)ので上記のC言語流のブールコンテキストのイディオムは使えません。 JavaScriptではnが0に等しいかどうかは n === 0 のように ===(厳密比較演算子)を使います。 === は==(比較演算子)とは異なり暗黙の型変換は行われず厳密に(この場合は 0 と)等しいかを評価します。


  • 例題

整数の偶奇判別プログラムを書け。

  • 解答

if/else文を用いる場合は、

"use strict";

if (n % 2 == 0) {
  console.log("偶数");
} else {
  console.log("奇数");
}

または

"use strict";

if (n % 2) {
  console.log("奇数");
} else {
  console.log("偶数");
}

条件演算子を用いる場合は、

"use strict";

console.log(n % 2 == 0 ? "偶数"
                       : "奇数");

または

"use strict";

console.log(n % 2 ? "奇数"
                  : "偶数");

または

"use strict";

console.log((n % 2 ? "奇"
                   : "偶") + "数");

または

"use strict";

console.log("偶奇"[n % 2] + "数");

など。

if-else 文の構文[編集]

if ( 条件式 ) 文1 [ else 文2 ]

[から]までは省略可能を意味し、この場合は「else節は省略可能」を意味します。

switch[編集]

switch文( switch statement )は、if/else文を何個もつらねて書くことが冗長な場合にもちいられます。

"use strict";

if (keyCode == 37) {
  console.log("←");
} else if (keyCode == 38) {
  console.log("↑");
} else if (keyCode == 39) {
  console.log("→");
} else if (keyCode == 40) {
  console.log("↓");
} else {
  console.log("?");
}

これはswitch文を使って次のように書くことができます。

"use strict";

switch (keyCode) {
  case 37:
    console.log("←");
    break;
  case 38:
    console.log("↑");
    break;
  case 39:
    console.log("→");
    break;
  case 40:
    console.log("↓");
    break;
  default:
    console.log("?");
}

必ずcase節の最後にbreak文を書くのを忘れないでください。 なお、たいていのケースではswitch文を使わなくても、連想配列を応用したディスパッチテーブルで事足ります。 switch文はここぞというときに使ってください[3]

"use strict";

console.log(
  {
    37: "←",
    38: "↑",
    39: "→",
    40: "↓"
  }[keyCode] || "?"
);

switch 文の構文[編集]

switch () {
case 値1 :
    文1
case 値2 :
    文2
   
   
case 値n :
    文n
[default :
    文x]
}

switch文に与えられた式に一致するcase句の値を上から順に厳密一致厳密比較演算子で評価され、true を返すcase句に対応する文が実行され、break文などの中断制御文が見つからない限り次の文が実行されます

switch文の限界と限界突破

swicth文は、上記の通り与えられた式と厳密に一致するケースに対応する文を実行します。 この為、式に対応する範囲や正規表現を直接的に表現することはできません。 この制限はややトリッキーな方法で回避できます。

let age = prompt("年齢は?"),
  text = "";
switch (true) {
case age <  3: text = "baby";         break;
case age <  7: text = "little child"; break;
case age < 13: text = "child";        break;
case age < 18: text = "youth";        break;
default :      text = "adult";        break;
}
console.log(text);

ポイントは

switch (true) {

と式として true を与えているところで

真となっている式を持ったケース節を上から探す

と言う動作を悪用利用しています。


反復[編集]

while[編集]

いよいよループの登場です。while文( while statement )は条件が真である間、文を実行しつづけます。

"use strict";

let i = 0;
while (i < 10) {
  console.log(i);
  i++;
}
実行結果
0
1
2
3
4
5
6
7
8 
9
  1. strictモードは常に使うべきです。
  2.  
  3. 変数iに0を代入
  4. i < 10がtrueでれば以下を実行します
    そうでなければ、このwhile文の次に進みます
  5. iを表示します
  6. iを1増やします。

10回ループが回るとiが10になり、i < 10がfalseになるのでループを抜けます。 このようにwhile文はi < 10がtrueである間、ブロックを実行しつづけます。 このようにして、プログラムは0から9までの数字を表示します(10に達するとループから脱出します)。

while 文の構文[編集]

while (  ) 

while文に与えられた式が truthy の間、繰り返し文を実行します。

do-while[編集]

do-while文( do-while statement )は、まずdo文のブロックを実行し、次にwhile文の条件式を確認してループします。次のプログラムは0から9までの数字を表示します。

let i = 0;
do {
    console.log(i);
    i++;
} while (i < 10);

do-while 文の構文[編集]

do  while (  )

まず文を無条件に実行し、while文に与えられた式が truthy の間、繰り返し文を実行します。

for[編集]

for文( for statement )は、いわゆるC言語スタイルのfor文です。 let i = 0のような変数の初期化と、i < 10のような条件式と、i++のような変数の更新を一行で書く制御構文です。 JavaScriptではwhile文やdo-while文はあまり使われませんが、for文はループを簡潔に書けるので非常に重宝します。 次のプログラムは0から9までの数字を表示します。

"use strict";

for (let i = 0; i < 10; i++) {
  console.log(i);
}
最初の項、let i = 0はループに入る前に一度だけ実行されます。
二番目の式、i < 10がtruthyならばブロックを実行します。
三番目の式i++を実行して再び条件式に戻ります。

結果、i < 10がtruthyである間、ブロックの実行と変数の更新が行われます。

"use strict";

const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for (let i = 0, len = array.length; i < len; i++) {
  console.log(array[i]);
}

ブロック文は文の特殊なケースで単文でももちろん有効です[4]

"use strict";

const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for (let i = 0, len = array.length; i < len; i++) console.log(array[i]);

後置インクリメント演算子は変数の値を1増やし、増やす前の値を返すので、次のように書けます。

"use strict";

const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for (let i = 0, len = array.length; i < len; console.log(array[i++]));

このような書き方を好み人もいますが、文意を汲むなら...

"use strict";

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach((x) => console.log(x));

のような、iterableオブジェクト[5]イテレーションメソッドを使うことも検討に値します。

for 文の構文[編集]

for ( 項1 ; 式2 ; 式3 ) 
  • 項1を評価します
  • 式2が turuthy な間、以下を繰り返します
    • 文 を実行します
    • 式3 を評価します

for-in[編集]

for-in 文は、オブジェクトのプロパティのうちキーが文字列で列挙可能なもの全てに反復処理を行います。

"use strict";

const obj = {
  x: 2,
  y: 3,
  z: 5
};
for (const prop in obj) {
  console.log(`${prop}: ${obj[prop]}`);
}
実行結果
x: 2
y: 3 
z: 5

for-of[編集]

for-of 文は、Iterableオブジェクト(たとえば String Array や NodeList)に対して、反復処理を行います[6]

for-of
"use strict";

const ary = [..."XYZ"];
for (const el of ary) {
  console.log(el);
}
実行結果
X
Y 
Z

Iterableでないオブジェクトが右の項に与えらてた場合、TypeError が throw されます。

Objectはfor-of不可
"use strict";

const obj = {
  a: 0,
  b: 1,
  c: 2
};
for (const el of obj) {
  console.log(el);
}
Object はItableではないので
実行時エラー
/workspace/Main.js:8
for (const el of obj) {
                 ^

TypeError: obj is not iterable
    at Object.<anonymous> (/workspace/Main.js:8:18)
    at Module._compile (node:internal/modules/cjs/loader:1126:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1180:10)
    at Module.load (node:internal/modules/cjs/loader:1004:32)
    at Function.Module._load (node:internal/modules/cjs/loader:839:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12) 
at node:internal/main/run_main_module:17:47
となります。
for-ofとObjectの分割代入の併用
"use strict";

const AddrBook = [
  {
    name: "tom",
    postnumber: "420-2410",
    age: 18
  },
  {
    name: "joe",
    postnumber: "420-0824",
    age: 17
  }
];
for (const { name, age } of AddrBook) {
  console.log(`${name}: ${age}`);
}

/** 同じコンセプトのArray::forEach */
AddrBook.forEach(({ name, age }) => console.log(`${name}: ${age}`));
実行結果
tom: 18 
joe: 17
tom: 18 
joe: 17

for await-of[編集]

for await-of 文は、非同期関数用の for-of です。

"use strict";

async function* asyncShift() {
  for (let i = 1, len = 2 ** 16; i < len; yield (i <<= 3));
}
(async function () {
  for await (const num of asyncShift()) {
    console.log(num);
  }
})();
実行結果
8
64
512
4096
32768 
262144

for each-in[編集]

for each-in 文はJavaScript 1.6でECMAScript for XML(E4X)のサポートの一環で導入されましたが、E4Xの廃止を受け非推奨を経て廃止されました。 下のプログラム例もモダンブラウザでは SyntaxError となります。for … of 文を使うようにして下さい。
JavaScript 1.6で追加されたfor each-in文はオブジェクトの値を順番に取り出して反復処理します。

var sales = <sales vendor="John">
    <item type="peas" price="4" quantity="6"/>
    <item type="carrot" price="3" quantity="10"/>
    <item type="chips" price="5" quantity="3"/>
  </sales>;
for each(var price in sales..@price) {
    console.log(price);
}
/*
 4
 3
 5
 */

反復制御[編集]

反復処理中に、反復を中断したり、「次の」反復にすぐに移りたい場合があります。 このようなときは、反復制御構文を使います。

break[編集]

break文( break statement )はループまたはswitch文を途中で抜けます。 次のプログラムはiが5になった時点でfor文のループを抜けるので、0から4までの数字を表示します。

"use strict";

for (let i = 0; i < 10; i++) {
  if (i == 5) break;
  console.log(i);
}

continue[編集]

continue文( continue statement )はループを次に進めます。 次のプログラムは0から9までの数字のうち3の倍数だけ表示します。

"use strict";

for (let i = 0; i < 10; i++) {
  if (i % 3) continue;
  console.log(i);
}

ラベル[編集]

ラベルを使用すると深いループを一気に抜けることができます。

ラベルの使用例
"use strict";

let LOOP = "Global variable";
LOOP: for (let x = 0; x < 10; x++) {
  for (let y = 0; y < 10; y++) {
    if (y === 5) break LOOP;
    console.log([x, y]);
  }
}
console.log(LOOP);
実行結果
[ 0, 0 ]
[ 0, 1 ]
[ 0, 2 ]
[ 0, 3 ]
[ 0, 4 ]
Global variable
二重ループからの脱出(大域脱出)ができていることが判ると思います。
ラベルも識別子のルールに従います(先頭一文字は英字あるいは '_'、英数字あるいは '_')
関数と変数やクラス名は同じ名前空間なので名前の衝突はできませんが、ラベルは別の名前空間なので衝突しても構いません。
URLが有効なJavaScript?

次は有効な JavaScript のコードです。

URLが有効なJavaScript?
"use strict";

https://ja.wikibooks.org/
for (let i = 0; i < 3; i++) {
    console.log(i);
}

1行目の URL がエラーになりません。

この行は

識別名 https のラベル と // で始まる単行コメント

と解されます。

重大なバグにはなりそうにないですが、モヤモヤしますね。


その他の制御文[編集]

with文[編集]

曖昧さを持ち込むためwith 文の使用は推奨されない。 また、strictモードでは SyntaxError となります。

with文の用途は、実際のコードを見ると良い。次の2つの関数は同じ意味です。

withの使用例
function math1() {
  console.log(Math.ceil(10.5)); // 小数点切り上げの値を表示
  console.log(Math.PI);         // 円周率πを表示
}

function math2() {
  with (Math) {
    console.log(ceil(10.5)); // 小数点切り上げの値を表示
    console.log(PI);         // 円周率πを表示
  }
}
math1();
math2();
実行結果
11
3.141592653589793
11
3.141592653589793

この様に、あらかじめオブジェクトをwith文で指定することで、それに続くブロックの間では、オブジェクトの名前を省略することが出来ます。 これは Pascal などの言語から移入されたものです。 with は、with により修飾名が省略されたことにより識別子に曖昧さを持ち込む事で、意図しないプロパティが使われるなど問題を生じることが知られており、with 文の使用は推奨されない。 また、strictモードでは SyntaxError となります。

脚註[編集]

  1. ^ 計算機科学では一般に0を自然数に含め、曖昧さを避けたいときは非負整数とも言います
  2. ^ falsy
  3. ^ JavaScriptのswitch文は、動的なのに静的なC言語の構文を倣ったのでコラム:switch文の限界と限界突破の様なハックを使わない限り恩恵を受けられません。
  4. ^ 文 ⊇ ブロック文
  5. ^ この場合は Array オブジェクト
  6. ^ ES2015で追加

外部リンク[編集]


このページ「JavaScript/制御構造」は、まだ書きかけです。加筆・訂正など、協力いただける皆様の編集を心からお待ちしております。また、ご意見などがありましたら、お気軽にトークページへどうぞ。