JavaScript/制御構造
制御構造
[編集]プログラムはしばしば、特定の条件に基づいた決定や同じコードの反復が必要な場面に直面します。JavaScriptでは、このような状況に対処するために「制御構造」が利用されます。制御構造は、プログラムの実行フローを操作し、条件に応じてコードを分岐させたり、同じ処理を繰り返すための手段です。
- 条件分岐
- 条件分岐は、ある条件が成り立つかどうかに応じて、異なるコードブロックを実行します。これにより、プログラムはさまざまな状況に対応でき、柔軟性を持たせることができます。if文やelse文それにswitch文を利用して条件分岐を行います。
- ループ構造
- ループ構造は、同じ処理を何度も繰り返すための仕組みです。これにより、反復的な作業やデータの処理が容易になります。for文やwhile文を使用して、プログラムが指定された条件を満たす限り繰り返し処理を行います。
JavaScriptの制御構造を理解することは、効果的で効率的なプログラミングの基本となります。これにより、コードの柔軟性が向上し、複雑なタスクにも対応できるようになります。次のステップでは、具体的な例を通じて制御構造の活用方法を学び、より高度なプログラミングスキルを養っていきましょう。
条件分岐
[編集]次のプログラムは、、
- 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
このコードは、「NaN(Not a Number)」という特殊な数値を扱うものです。まず、「use strict」が指定されているため、コードは厳密モードで実行されます。
次に、0を0で割った結果がnに格納されています。JavaScriptでは、0を0で割るとNaNが返ります[1]。その後、nが負の数より小さいか、正の数より大きいか、0より大きいか、以上の条件分岐が行われます。
しかし、NaNは数値としては未定義であるため、比較演算子(<、>、=)を使って大小を比較することはできません。そのため、if文の条件式は一つでも成り立たなくなり、最後のelse節が実行されます。その結果、コンソールには「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より小さい)という条件が真であれば「負の数」、さもなくば「自然数」と表示します[2]。
- 例
"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になるので[3]、次のif文のブロックは絶対に実行されません(デッドコード)。
if (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文はここぞというときに使ってください[4]。
"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文などの中断制御文が見つからない限り次の文が実行されます。
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
- strictモードは常に使うべきです。
- 変数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までの数字を表示します。
"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]); }
ブロック文は文の特殊なケースで単文でももちろん有効です[5]。
"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オブジェクト[6]のイテレーションメソッドを使うことも検討に値します。
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)に対して、反復処理を行います[7]。
- 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
- 二重ループからの脱出(大域脱出)ができていることが判ると思います。
- ラベルも識別子のルールに従います(先頭一文字は英字あるいは '_'、英数字あるいは '_')
- 関数と変数やクラス名は同じ名前空間なので名前の衝突はできませんが、ラベルは別の名前空間なので衝突しても構いません。
次は有効な 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 となります。
しかし、with文は次のような問題があるため、推奨されないことが多くなっています。
- スコープの曖昧さ:with文を使うと、変数がどこで定義されているか分からなくなることがあります。プロパティ名が変数名と同じ場合には、予期しない変数の値を参照することになることがあります。
- パフォーマンスの低下:with文を使うと、コンパイラがコードを最適化しにくくなります。特に、動的にオブジェクトを変更する場合には、パフォーマンスの低下が顕著になることがあります。
- セキュリティリスク:with文を使うと、コードを実行する際に使用されるオブジェクトが制御できなくなることがあります。外部から渡されたオブジェクトをwith文で参照する場合、そのオブジェクトが悪意のあるプロパティを持っている場合、コードの動作が予期しないものになることがあります。
附録
[編集]チートシート
[編集]// strictモード "use strict"; // if文 if (condition) { // trueの場合の処理 } else { // falseの場合の処理 } // switch文 switch (expression) { case value1: // value1の場合の処理 break; case value2: // value2の場合の処理 break; default: // 上記の値に当てはまらない場合の処理 } // for文 for (let i = 0; i < length; i++) { // 繰り返す処理 } // for...of文 for (let item of iterable) { // 繰り返す処理 } // while文 while (condition) { // 条件がtrueの場合の処理 } // do...while文 do { // 条件がtrueの場合の処理 } while (condition); // break文 break; // continue文 continue; // ラベル付き文 label: { // ラベル付きの処理 } // throw文 throw expression; // try...catch文 try { // 処理 } catch (error) { // エラーが発生した場合の処理 } finally { // 最後に実行する処理 } // with文 with (expression) { // 処理 }
用語集
[編集]- 制御構造 (Control structures): プログラムの実行を制御するための構造。
- 分岐構造 (Branching structures): 条件に応じてプログラムの実行を変更するための構造。if文、switch文などがある。
- 繰り返し構造 (Looping structures): 同じ処理を繰り返し実行するための構造。for文、while文、do-while文などがある。
- break文 (break statement): ループやswitch文の処理から脱出するために使用する文。
- continue文 (continue statement): ループの現在のイテレーションをスキップし、次のイテレーションに進むために使用する文。
- ラベル (Label): コードブロックに名前を付け、break文やcontinue文でジャンプするために使用する識別子。
- throw文 (throw statement): 例外を投げるために使用する文。
- try-catch文 (try-catch statement): 例外をキャッチし、例外が発生した場合に代替処理を実行するために使用する文。
- finally節 (finally clause): try-catch文の最後に実行される節。
- return文 (return statement): 関数から値を返すために使用する文。
脚註
[編集]- ^
0 / 0
は、除数がゼロであると考えれば無限大ですが、被除数がゼロと考えるとゼロというパラドックス持ちで、ISO/IEC/IEEE 60559:2011 ではこのケースを NaN 非数とします。 - ^ 計算機科学では一般に0を自然数に含め、曖昧さを避けたいときは非負整数とも言います
- ^ falsy
- ^ JavaScriptのswitch文は、動的なのに静的なC言語の構文を倣ったのでコラム:switch文の限界と限界突破の様なハックを使わない限り恩恵を受けられません。
- ^ 文 ⊇ ブロック文
- ^ この場合は Array オブジェクト
- ^ ES2015で追加