JavaScript基礎 #3 制御フロー

#2 変数と型 で値を扱う道具を見ました。今回の記事は、その値を使って — 分岐繰り返し を作るところです。

if / else if / else #

一番なじみのある分岐です。

if 基本
const score = 85;

if (score >= 90) {
  console.log('A');
} else if (score >= 80) {
  console.log('B');
} else if (score >= 70) {
  console.log('C');
} else {
  console.log('F');
}

条件は truthy/falsy 検査 になります。#2 で見た falsy の7つ(false, 0, -0, 0n, ‘’, null, undefined, NaN)であれば、else に進みます。

truthy 検査の慣用句
const name = 'カーティス';

if (name) {
  // name が空でない文字列のとき
  console.log(`こんにちは, ${name}`);
}

// ほぼ同じ — 明示的
if (name !== '') {
  console.log(`こんにちは, ${name}`);
}

短く意図が明確 なときは truthy 検査を、何が空なのか をはっきりさせたいときは明示的な比較を。どちらもよく見られます。

三項演算子 — 短い分岐に向く表現 #

三項演算子
const score = 85;
const grade = score >= 60 ? '合格' : '不合格';
//             条件       ? 真のとき : 偽のとき

値を決める場面(変数代入、JSX、オブジェクトのフィールド)では、if/else より短くて読みやすくなります。ただし2段以上ネストするとすぐに読みにくくなるので、その場合は if/else if で展開して書くほうが良いです。

whiledo...while #

条件が真である間、繰り返します。

while
let count = 0;
while (count < 5) {
  console.log(count);
  count = count + 1;
}
// 0, 1, 2, 3, 4

do...while最低1回は必ず実行される 変形です。ユーザー入力のように「受け取ってから条件を検査する」場面に向いています。

do...while
let answer;
do {
  answer = prompt('数字を入力してください');
} while (isNaN(Number(answer)));

for — クラシックループ #

3つの欄で構成されます — 初期化 ; 条件 ; 各反復後

古典 for
for (let i = 0; i < 5; i = i + 1) {
  console.log(i);
}
// 0, 1, 2, 3, 4

C/Java と同じ形です。インデックスを直接扱う必要があるとき(逆順、スキップ、2つの配列を同時に進めるなど)は、依然として最も明確です。ただし、通常の配列の反復は for...of のほうが普通は読みやすくなります

for...of — 配列イテレーションの標準 #

for...of — モダン標準
const fruits = ['りんご', 'バナナ', 'ぶどう'];

for (const fruit of fruits) {
  console.log(fruit);
}
// りんご, バナナ, ぶどう

配列の を1つずつ取り出してくれます。インデックスが不要なら、ほぼ常にこちらのほうがすっきりします。

インデックスも一緒に — entries() #

値とインデックスを同時に
const fruits = ['りんご', 'バナナ', 'ぶどう'];

for (const [i, fruit] of fruits.entries()) {
  console.log(`${i}: ${fruit}`);
}
// 0: りんご, 1: バナナ, 2: ぶどう

fruits.entries()[インデックス, 値] のペアを作ってくれるイテレータです。分割代入は #5 オブジェクトと配列 で詳しく扱いますが、ひとまず「2つの変数に同時に受け取る構文」と覚えておけば大丈夫です。

for...in — ほとんど使わない変形 #

名前が似ていて混乱しやすいですが、これは オブジェクトのキー を反復します。配列に使うとよくないことがよく起こります。

for...in (オブジェクトに)
const user = { id: 'u1', name: 'カーティス', age: 30 };

for (const key in user) {
  console.log(`${key}: ${user[key]}`);
}
// id: u1, name: カーティス, age: 30
for...in を配列に使うと事故
const arr = ['a', 'b', 'c'];
arr.foo = 'oops';

for (const key in arr) {
  console.log(key);
}
// 0, 1, 2, foo  ← foo も出る

配列のインデックス(012)と一緒に、私たちが追加したプロパティ(foo)も入ってきます。そのため、配列は絶対に for...in で反復しないでください。オブジェクトには Object.keys/values/entries を使うほうが安全です。

オブジェクト反復 — 推奨パターン
const user = { id: 'u1', name: 'カーティス', age: 30 };

for (const [key, value] of Object.entries(user)) {
  console.log(`${key}: ${value}`);
}

配列のメソッドによる反復 — forEach, map, filter プレビュー #

for...of のほかに、配列は関数型スタイルの反復メソッドを持ちます。

forEach / map / filter プレビュー
const numbers = [1, 2, 3, 4, 5];

// 副作用のみ — forEach
numbers.forEach((n) => console.log(n));

// 変換 — map
const doubled = numbers.map((n) => n * 2);
// [2, 4, 6, 8, 10]

// 絞り込み — filter
const evens = numbers.filter((n) => n % 2 === 0);
// [2, 4]

これらのメソッドは #5 オブジェクトと配列 で本格的に扱います。今は「forループ以外にも表現方法がある」ということだけ覚えておいてください。

breakcontinue #

ループの中で流れを制御する2つのキーワードです。

break — ループを抜け出す
for (let i = 0; i < 10; i = i + 1) {
  if (i === 5) break;
  console.log(i);
}
// 0, 1, 2, 3, 4
continue — 次の反復へ
for (let i = 0; i < 5; i = i + 1) {
  if (i === 2) continue;
  console.log(i);
}
// 0, 1, 3, 4 (2 が抜ける)

forEach/map/filter のようなメソッドの中では、break/continue は動作しません。途中で打ち切る必要があるなら、for...of で書き直すか、some/every のようなメソッド(次のシリーズで扱います)を使います。

switch — 複数の値での分岐 #

switch 基本
const day = 'mon';

switch (day) {
  case 'mon':
  case 'tue':
  case 'wed':
  case 'thu':
  case 'fri':
    console.log('平日');
    break;
  case 'sat':
  case 'sun':
    console.log('週末');
    break;
  default:
    console.log('不明');
}

case は上から下へ検査され、一致したところから break に出会うまで実行が続きます。これがよく落とし穴になる部分です — break を忘れると、次の case も一緒に実行されてしまいます(フォールスルー)。

break を忘れた事故
switch (day) {
  case 'mon':
    console.log('月曜日!');
    // break 忘れ
  case 'tue':
    console.log('火曜日!');
    break;
}
// day が 'mon' なら — 月曜日! 火曜日! 両方出力

switchの比較は厳密(===) #

switch のcaseマッチングは、常に === で行われます。自動変換は起こりません。

厳密な比較
switch (5) {
  case '5': console.log('a'); break;  // 一致しない
  case 5:   console.log('b'); break;  // ここでマッチ
}

これは良い点です — == の落とし穴から自由になります。

短いifの落とし穴 — 中括弧の省略 #

JavaScriptは短いifで中括弧を省略できます。

短い if — 危険
if (condition)
  doSomething();

問題は、2行以上に増えたとき に事故が起きることです。

中括弧がないと事故
if (condition)
  doSomething();
  alsoThis();    // 常に実行される (if と無関係)

インデントは if の中のように見えますが、実際は常に実行されます。こうした事故を防ぐため、短いifでも常に中括弧 を使うコンベンションが安全です。ESLintの curly ルールが、まさにこれを強制してくれます。

まとめ #

今回の記事で整理した内容:

  • if/else は truthy/falsy 検査または明示的な比較
  • 短い分岐は三項演算子、それ以上は if/else if
  • 通常の配列の反復は for...of が標準
  • for...in はオブジェクトのキーの反復 — 配列には使わない
  • break/continue は通常のループでのみ。メソッドのコールバックでは効かない
  • switch は fallthrough に注意、比較は常に ===
  • 短いifでも中括弧を付けるほうが安全

次の記事(#4 関数)では、JavaScriptの関数 — 宣言/式/アロー の3つの定義方法、仮引数のパターン、そしてホイスティングとは何かを扱います。

X