JavaScript/制御構造

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

制御構造(せいぎょこうぞう、control flow)とは、「順次」「分岐」「反復」という基本的な処理のことを言います。

概要[編集]

次のプログラムは、、

  • nが0より小さいときには「負の数」
  • nが0より大きいときには「正の数」
  • いづれにもあてはまらないなら(つまり nが0、あるいは数値に暗黙の変換ができないときには)「0」

と表示します。

let n = 0;

if (n < 0) {
    console.log("負の数");
}
else if (n > 0) {
    console.log("正の数");
}
else {
    console.log("0");
}

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

さあ、この章で学んで行きましょう。

if-else[編集]

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

let n = -1;

if (n < 0) {
    console.log("負の数");
}

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

let n = 0;

if (n < 0) {
    console.log("負の数");
}
else {
    console.log("自然数");
}

if文とelse節をあわせてif/else文と呼びます。else節は必ず直前のif文に対応するので、if文とelse節の間に余計な文を入れることはできません。 if/else文は次のように何個もつらねることができます。

let n = 0;

if (n < 0) {
    console.log("負の数");
}
else if (n > 0) {
    console.log("正の数");
}
else {
    console.log("0");
}

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

let n = 0;

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

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

let n = 0;

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

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文を用いる場合は、

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

または

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

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

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

または

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

または

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

または

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

など。

if-else 文の構文[編集]

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

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

switch[編集]

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

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文を使って次のように書くことができます。

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]

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)は条件が真である間、文を実行しつづけます。

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

iはinteger(整数)の頭文字です。このプログラムはまず、

  • 1行目で変数iに0を代入しています。
  • 次にi < 10がtrueでれば以下を実行します(そうでなければ、このwhile文の次に進みます)。
    • iを表示します
    • 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までの数字を表示します。

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

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

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

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]

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増やし、増やす前の値を返すので、次のように書けます。

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++]))
  ;

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

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

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
const ary = [..."XYZ"];

for (const el of ary) {
    console.log(el);
}
// X
// Y
// Z

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

Objectはfor-of不可
const obj = { a: 0, b: 1, c: 2};

for (const el of obj) {
    console.log(el);
}

Object はItableではないので

実行時エラー
TypeError: obj is not iterable
となる。
for-ofとObjectの分割代入の併用
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 です。

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までの数字を表示します。

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

continue[編集]

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

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

ラベル[編集]

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

ラベルの使用例
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?
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/制御構造」は、まだ書きかけです。加筆・訂正など、協力いただける皆様の編集を心からお待ちしております。また、ご意見などがありましたら、お気軽にトークページへどうぞ。