3.1 if〜else文

(1/1)
if~else文のフロー図と陽奈
AI生成コンテンツ / AI-generated content
JavaScriptを含むプログラミング言語には制御という仕組みが備わっており、条件によって計算式を切り換えたり、同じ計算式を繰り返し実行することができる。
制御を活用することで、Excel関数だけでは実現できなかった、さまざまな処理が実現できるようになる。
まず、if~else文の使い方から学んでいこう。あわせて、JavaScriptの文とブロック文について説明する。プログラムの内容も、少しずつ実用的なものへステップアップしていく。

目次

サンプル・プログラム

4年に一度のうるう年

4年に一度のうるう年――入力した西暦年がうるう年かどうかを判定するプログラムが "ifElse1.html" である。

ifElse1.html

  19: <script>
  20: /**
  21:  * 判定と画面表示
  22: */
  23: function ifElse1() {
  24:     //変数宣言
  25:     const year = Number.parseInt(document.getElementById('year').value, 10);    //西暦年
  26:     if (!Number.isInteger(year)) {
  27:         document.getElementById('ret').textContent = '整数を入力してください';
  28:         return;
  29:     }
  30: 
  31:     //うるう年判定(間違いがある)
  32:     let ret = '平年';
  33:     if (year % 4 === 0) {
  34:         ret = 'うるう年';
  35:     }
  36: 
  37:     //結果を表示する
  38:     document.getElementById('ret').textContent = ret;
  39: }
  40: </script>

これまで何度か出てきた if文を使って、「4年に一度」の判定をプログラムに実装した。
if文は if (条件式) のように用い、条件式が真(truthy)と評価されたとき、それに続くブロック {...} で囲まれた処理を実行する。

このプログラムでは、まず、判定結果を格納するString型変数 ret に '平年' を代入しておく。
次に、if文を使い、西暦年が格納されたNumber型変数 year を4で割った余りを計算し、計算結果が0(4で割り切れる)の場合は、変数 ret に 'うるう年' を代入する。
最後に、変数 ret の内容を表示する。

正確なうるう年判定

「4年に一度、うるう年」というのは、じつは不正確である。
現在の西暦であるグレゴリオ暦で、うるう年は次のように定義されている。
  1. 4で割り切れる年はうるう年である。
  2. ただし、100で割り切れる年は平年である。
  3. ただし、400で割り切れる年はうるう年である。
西暦2000年は400で割り切れたので、うるう年だった。西暦2100年は100で割り切れるので、平年である。

ifElse2.html

  19: <script>
  20: /**
  21:  * 判定と画面表示
  22: */
  23: function ifElse2() {
  24:     //変数宣言
  25:     const year = Number.parseInt(document.getElementById('year').value, 10);    //西暦年
  26:     if (!Number.isInteger(year)) {
  27:         document.getElementById('ret').textContent = '整数を入力してください';
  28:         return;
  29:     }
  30:     let ret;
  31: 
  32:     //うるう年判定(正確なもの)
  33:     if (year % 400 === 0) {
  34:         ret = 'うるう年';
  35:     } else if (year % 100 === 0) {
  36:         ret = '平年';
  37:     } else if (year % 4 === 0) {
  38:         ret = 'うるう年';
  39:     } else {
  40:         ret = '平年';
  41:     }
  42: 
  43:     //結果を表示する
  44:     document.getElementById('ret').textContent = ret;
  45: }
  46: </script>

これをプログラムにしたのが "ifElse2.html" である。
if~else文は次のようにして使う。
if (式1) {
    ブロック1
} else if (式2) {
    ブロック2
} else {
    ブロック3
}
  1. もし式1がtrueならブロック1を実行する。
  2. 式1がfalseであり、式2がtrueならブロック2を実行する
  3. 式1と式2がともにfalseならブロック3を実行する。
else if節は必要な数だけ追加でき、省略もできる。else節も不要なら省略できる。

ここで if~else文 の順序に気をつけよう。
if (year % 4) を先頭に持ってくると、西暦2100年(令和82年)もうるう年になってしまう。
まず、グレゴリオ暦の定義を次のように並べ替える必要がある。
  1. もし西暦年を400で割り切れれば、うるう年である。
  2. そうでなければ、西暦年を100で割り切れれば、平年である。
  3. そうでなければ、西暦年を4で割り切れれば、うるう年である。
  4. そうでなければ、平年である。
フロー図にすると下図のようになる。
うるう年の判定
うるう年の判定

式とブロック文

JavaScriptでは、処理の単位を文(statement)と呼ぶ。文の終わりにはセミコロン ; を書く。JavaScriptが一定の規則でセミコロンを補う場合もあるが、意図しない解釈を避けるため、本講座では明示する。
ret = 'うるう年';
複数の文をまとめてブレース {...} で区切ったものをブロック文と呼ぶ。
if~else文 は、条件によって、それに続くブロック文を実行するか否かを制御していると言える。

なお、"ifElse2.html" のように各分岐で実行する文が1つだけの場合、ブレース {...} を省略して次のように書くこともできる。
if (year % 400 === 0)      ret = 'うるう年';
else if (year % 100 === 0) ret = '平年';
else if (year % 4 === 0)   ret = 'うるう年';
else                      ret = '平年';
しかし、あとでプログラムを修正するときに文を追加しても誤動作しないよう、最初からブレース {...} で囲む方が安全で読みやすい。
「2.1 変数と定数 - グローバル変数とローカル変数」において、let 宣言した変数は、宣言したブロック内(ブロックスコープ)で通用するローカル変数になると説明した。
制御を伴わないブロック文を使って、この作用を確認しておこう。

let1.html

  19: <script>
  20: //ページのロード時に実行
  21: window.onload = function() {
  22:     let i = 1;                  //変数宣言・その1
  23:     {
  24:         let i = 2;              //変数宣言・その2
  25:         {
  26:             let i = 3;          //変数宣言・その3
  27:             document.getElementById('let1').innerHTML = i;
  28:         }
  29:         document.getElementById('let2').innerHTML = i;
  30:     }
  31:     document.getElementById('let3').innerHTML = i;
  32: }
  33: </script>

ローカル変数 i を3つ宣言しているが、各々の i は、お互いに影響を及ぼさないことがわかる。
ブロック文の中でのみ必要となる作業用変数は、ブロック文の中で let 宣言 して使うといい。

練習問題:if〜else文

練習問題:セミコロンの役割

コラム:ExcelのIF関数、他言語の制御文、フロー図

Excelアイコン
ExcelにもIF関数が備わっている。
うるう年の判定は、=IF(MOD(A1,400)=0,"うるう年",IF(MOD(A1,100)=0,"平年",IF(MOD(A1,4)=0,"うるう年","平年"))) とすることで実現はできるが、かなり読みにくくなってしまう。JavaScriptのように複数行にわたって書くことができるブロック文を制御する方が、読みやすい。
"ifElse2.html" と同じ処理をPythonで書いたものが "ifElse3.py" である。Pythonでは、ブロックの範囲をインデントで表す。
ifElse3.py
# うるう年判定(正確なもの)
if year % 400 == 0:
	ret = 'うるう年'
elif year % 100 == 0:
	ret = '平年'
elif year % 4 == 0:
	ret = 'うるう年'
else:
	ret = '平年'

PHPの実装例は「PHPで閏年(うるう年)かどうか判定する」をご覧いただきたい。
他の言語も、おおむねJavaScriptと書き方は似ている。
制御の流れを表すのに、よくフロー図(流れ図、フローチャート)を使う。本文中でも紹介した。
「1.3 四則計算と変数」のコラムで紹介したプログラミング電卓付属の「プログラム・ライブラリ」にもフロー図が掲載されており、とても参考になった。
プログラミングに慣れるまでは、手間を惜しまず、自力でフロー図を書くことをお勧めする。

また、プログラミングに慣れた方でも、プログラムをフロー図にして見直すことで、重複する条件を整理し、制御の流れをシンプルにできる場合がある。
ただし、コードは短ければ必ずバグが少ないわけではない。読みやすさやテストのしやすさも考え、自作プログラムを見直してみてはいかがだろうか。

コラム:グローバルスコープ、関数スコープ、ブロックスコープ

本文で紹介したように、変数には、それを参照できる範囲(スコープ)がある。ここでは、次の3種類を取り上げる。
  1. グローバルスコープ
  2. 関数スコープ
  3. ブロックスコープ
それぞれのスコープについて、サンプル・プログラム "scope1.html" を使って動きを見てみよう。

scope1.html

  52: <script>
  53: // オブジェクトを描画後に実行する
  54: let i = 1;                                              // グローバルスコープ
  55: document.getElementById('let1').innerHTML = i;          // ‥‥(1)
  56: window.x = 'x';                                         // グローバルスコープ
  57: document.getElementById('let2').innerHTML = window.x;   // ‥‥(2)
  58: // 関数呼び出し
  59: hoge();
  60: document.getElementById('let8').innerHTML = i;          // ‥‥(8)
  61: // 関数定義
  62: function hoge() {
  63:     let i = 2;                                          // 関数スコープ
  64:     document.getElementById('let3').innerHTML = i;      // ‥‥(3)
  65:     if (true) {
  66:         let i = 3;                                      // ブロックスコープ
  67:         document.getElementById('let4').innerHTML = i;  // ‥‥(4)
  68:     }
  69:     document.getElementById('let5').innerHTML = i;      // ‥‥(5)
  70:     document.getElementById('let6').innerHTML = window.x;   // ‥‥(6)
  71:     document.getElementById('let7').innerHTML = x;          // ‥‥(7)
  72: }
  73: </script>

スコープが異なると、お互いに影響しないことがわかる。
const についても同様のことが言える。

ブラウザで動く通常のスクリプトでは、window がグローバルオブジェクトである。
ここでは window.x というプロパティに文字列 'x' を代入し、その動作を見ている(2)。グローバルオブジェクトのプロパティなので、関数内でも window.x として参照でき(6)、この例では window. を省略した x でも参照できる(7)。ただし、同名のローカル変数があれば、識別子 x は内側のスコープにある変数を指す。なお、トップレベルで let 宣言した変数はグローバルスコープを持つが、window のプロパティにはならない。

コラム:レキシカルスコープ

JavaScriptでは、変数をコード上のどこで宣言したかによって参照できる範囲が決まる。これをレキシカルスコープ(静的スコープ)と呼ぶ。
let や const をブロック内で宣言するとブロックスコープになり、関数内で宣言した変数はその関数の外から参照できない。通常のスクリプトで関数やブロックの外側に宣言した変数はグローバルスコープを持つ。JavaScriptモジュールには、これとは別にモジュールスコープがある。

では、次のように関数内で関数を宣言した場合、変数 a, b のスコープはどうなるか。

scope2.html

  46: <script>
  47: // オブジェクトを描画後に実行する
  48: // 関数定義
  49: function hoge() {
  50:     let a = 1;
  51:     document.getElementById('let1').innerHTML = a;          // ‥‥(1)
  52:     // 関数内で関数定義
  53:     function hogeSub() {
  54:         let b = 2;
  55:         document.getElementById('let2').innerHTML = b;      // ‥‥(2)
  56:         document.getElementById('let3').innerHTML = a;      // ‥‥(3)
  57:     }
  58:     // 関数実行
  59:     hogeSub();
  60:     // bは表示できるか?
  61:     document.getElementById('let4').innerHTML = b;          // ‥‥(4)
  62: }
  63: // 関数実行
  64: hoge();
  65: </script>

変数 b は内側の関数 hogeSub の中だけで参照できる。したがって、外側の hoge 関数から参照しようとすると(4)、コンソールに ReferenceError が出力される。
一方、外側の関数で宣言した変数 a は hogeSub からも参照できる(3)。内側のスコープから外側のスコープをたどって変数を探すためである。

JavaScriptは識別子を参照するとき、現在のレキシカル環境から外側の環境へ順に同名の識別子を探す。このつながりをスコープチェーンと呼ぶ。
レキシカルスコープは JavaScript を書いた時点に決定し、実行時に変化することはない。つまり、JavaScriptのスコープは静的で、動的ではない。
したがって、サンプル・プログラム "scope3.html" の結果は "foo" ではなく "global" となる。

scope3.html

  42: <script>
  43: // オブジェクトを描画後に実行する
  44: let x = 'global';
  45: 
  46: // 関数定義
  47: function hoge() {
  48:     document.getElementById('let1').innerHTML = x;          // ‥‥(1)
  49: }
  50: 
  51: // 関数定義
  52: function foo() {
  53:     let x = 'foo';
  54:     // 関数実行
  55:     hoge();
  56: }
  57: 
  58: // 関数実行
  59: foo();
  60: </script>

参考サイト

(この項おわり)
header