JavaScript 入門

JS 関数のデフォルト引数

関数を構築する際、柔軟性を持たせたいことが多々あります。特定の情報(実引数)を必要とする場合もあれば、情報が提供されなくても問題なく動作させたい場合もあります。

例えば、最終価格を計算する関数を想像してください。「基本価格」は常に必要ですが、「割引」や「税率」はオプションかもしれません。ユーザーが割引を指定しなかった場合はデフォルトで 0% に、税率を指定しなかった場合は標準税率(例:5%)に設定したいはずです。

ここで役立つのが デフォルト引数 (Default Parameters) です。

デフォルト引数を使用すると、関数定義の中でパラメータに直接 フォールバック値 (fallback value) を割り当てることができます。関数呼び出し時に実引数が渡されなかった場合、あるいは明示的に undefined が渡された場合に、このデフォルト値が適用されます。これにより、関数内で複雑な条件分岐を書く必要がなくなり、コードの堅牢性と使いやすさが向上します。

ES6(ECMAScript 2015)で導入されたこのモダンな機能は、コードを劇的にクリーンにし、オプション入力を処理するための明確な手法を提供します。

1. デフォルト引数の理解

デフォルト引数は、関数の呼び出し時に値が渡されない(または undefined が渡された)場合に、デフォルト値でパラメータを初期化する機能です。これによりエラーを防止し、欠落した引数に対して適応力の高い関数を作成できます。

1.2 デフォルト引数の構文

構文は非常に直感的です。関数のパラメータリスト内で、代入演算子 (=) を使って直接値を割り当てるだけです。

function greet(name = "ゲスト") {
  console.log(`こんにちは、 ${name}さん!`);
}

この例では、name パラメータのデフォルト値として "ゲスト" が設定されています。

1.3 デフォルト引数の動作原理

異なる呼び出し状況での greet 関数の挙動を確認してみましょう。

実引数を提供した場合: greet("アリス") と呼び出すと、name"アリス" になり、デフォルト値の "ゲスト" は無視されます。

greet("アリス"); // 出力: こんにちは、 アリスさん!

実引数を提供しない場合:greet() と呼び出すと、name には値が渡されません。この場合、JavaScript は自動的に undefinedname に割り当てます。パラメータが undefined を受け取るとデフォルト値が発動するため、name は "ゲスト" になります。

greet();        // 出力: こんにちは、 ゲストさん!

明示的に undefined を渡した場合: 実引数として undefined を渡した場合も、デフォルト値が使用されます。

greet(undefined); // 出力: こんにちは、 ゲストさん!

重要な違い:undefined はデフォルト値をトリガーしますが、他の「Falsyな値(偽値)」である null0、空文字 '' はトリガーしません。これらを渡すと、そのままパラメータに代入されます。

greet(null);      // 出力: こんにちは、 nullさん!
greet('');        // 出力: こんにちは、 さん!
greet(0);         // 出力: こんにちは、 0さん!

1.4 ES6 以前の書き方との比較

デフォルト引数が導入される前、開発者は関数内で手動で欠落した引数をチェックする必要がありました。これには論理和演算子 (||) や if 文が使われていました。

ES6 以前の手法( || を使用):

function calculateTotal(price, taxRate) {
  // taxRate が undefined, null, 0, 空文字の場合、デフォルトの 0.05 になる
  taxRate = taxRate || 0.05; // ここに問題があります。0 が有効な税率だったら?
  return price * (1 + taxRate);
}

console.log(calculateTotal(100));       // 出力: 105 (taxRate は 0.05)
console.log(calculateTotal(100, 0.10)); // 出力: 110
console.log(calculateTotal(100, 0));    // 出力: 105 (問題: 0 は有効な税率だが、|| がデフォルト値に変えてしまう)

見ての通り、||0(および null, '')を Falsy と見なすため、0 が正当な入力であってもデフォルト値が適用されてしまうという欠点があります。

ES6 以前の手法( if 文を使用):

function calculateTotalImproved(price, taxRate) {
  if (taxRate === undefined) { // 明示的に undefined をチェック
    taxRate = 0.05;
  }
  return price * (1 + taxRate);
}

console.log(calculateTotalImproved(100));       // 出力: 105
console.log(calculateTotalImproved(100, 0.10)); // 出力: 110
console.log(calculateTotalImproved(100, 0));    // 出力: 100 (正しい。0 が許容される)

この if 文によるチェックは || より堅牢ですが、コード量が増えてしまいます。デフォルト引数は、このロジックをより簡潔に、関数のシグネチャの一部として記述できるスマートな解決策です。

2. 引数の順番

デフォルト引数を使用する際の慣例として、デフォルト値を持つ引数は、持たない引数の後ろに配置すべきです。JavaScript ではどこにでも配置可能ですが、前に置くと呼び出しが非常に不自然になります。

ベストプラクティス(必須引数が前、デフォルト引数が後ろ):

function sendMessage(message, sender = "匿名希望", recipient = "全員") {
  console.log(`送信者: ${sender} 宛先: ${recipient} - メッセージ: "${message}"`);
}

sendMessage("こんにちは!");                   // 送信者: 匿名希望 宛先: 全員 - メッセージ: "こんにちは!"
sendMessage("重要なお知らせ", "管理者");        // 送信者: 管理者 宛先: 全員 - メッセージ: "重要なお知らせ"
sendMessage("内緒話", "ユーザー1", "ユーザー2"); // 送信者: ユーザー1 宛先: ユーザー2 - メッセージ: "内緒話"

不適切な例(デフォルト引数が前):

// 動作はしますが、sender に undefined を渡さない限り message を指定できません
function badSendMessage(sender = "匿名希望", message) {
  console.log(`送信者: ${sender} - メッセージ: "${message}"`);
}

// sender のデフォルト値を使いたい場合、最初の引数に undefined を渡す必要がある
badSendMessage(undefined, "ハロー"); // 送信者: 匿名希望 - メッセージ: "ハロー"
badSendMessage("管理者", "ハロー");   // 送信者: 管理者 - メッセージ: "ハロー"

// badSendMessage("ハロー"); 
// これだと "ハロー" が sender に割り当てられ、message が undefined になり失敗する

このように、デフォルト引数を先に配置すると、呼び出し側で意図的に undefined を渡す必要が出てくるため、直感的ではありません。デフォルト引数は必ずリストの末尾に置くようにしましょう。

3. デフォルト値としての式

デフォルト引数には数値や文字列のリテラルだけでなく、関数呼び出し、変数の参照、あるいはそれ以前に定義されたパラメータを含む 有効な JavaScript の式 を使用できます。これらの式は、デフォルト値が必要になったタイミングで初めて評価(実行)されます。

function createId(base = Math.random().toString(36).substring(2, 9)) {
  console.log(`生成された ID: ${base}`);
}

createId();         // 出力: 生成された ID: [ランダム文字列1]
createId();         // 出力: 生成された ID: [ランダム文字列2] (呼び出しごとに実行される)
createId("user-123"); // 出力: 生成された ID: user-123

また、パラメータリスト内の前の引数を後の引数のデフォルト値に使用することも可能です。

function greetUser(firstName, lastName = "Doe", fullName = `${firstName} ${lastName}`) {
  console.log(`こんにちは、 ${fullName}さん!`);
}

greetUser("John");                     // 出力: こんにちは、 John Doeさん!
greetUser("Jane", "Smith");            // 出力: こんにちは、 Jane Smithさん!
greetUser("Alice", "Wonderland", "Alice L. Wonderland"); // 出力: こんにちは、 Alice L. Wonderlandさん!

fullName のデフォルト値が firstNamelastName に依存している点に注目してください。これは非常に強力で柔軟な手法です。

4. 実践的な例とデモンストレーション

デフォルト引数を用いて、柔軟で堅牢な関数を作成する具体的なケースを見てみましょう。

4.1 例 1:ユーザープロフィールの表示設定

ユーザーの表示名やアバターを生成する関数です。一部の情報はオプションとなります。

/**
 * ユーザープロフィールの表示用文字列を生成する
 * @param {string} username - ユーザー固有のID(必須)
 * @param {string} displayName - 表示名。デフォルトは username
 * @param {string} avatarUrl - アバターURL。デフォルトは汎用画像
 * @param {boolean} isAdmin - 管理者フラグ。デフォルトは false
 */
function createUserProfileDisplay(username, displayName = username, avatarUrl = "/images/default-avatar.png", isAdmin = false) {
  let adminTag = isAdmin ? " (管理者)" : "";
  console.log(`--- ユーザープロフィール ---`);
  console.log(`ユーザーID: ${username}`);
  console.log(`表示名: ${displayName}${adminTag}`);
  console.log(`アバター: ${avatarUrl}`);
  console.log(`-------------------------\n`);
}

// ケース 1: 必須のユーザー名のみ
createUserProfileDisplay("john_doe");

// ケース 2: カスタム表示名
createUserProfileDisplay("jane_smith", "ジェーン S.");

// ケース 3: アバターURLも指定
createUserProfileDisplay("coder_x", "コード職人", "https://example.com/avatar.jpg");

// ケース 4: 全ての情報を指定(管理者)
createUserProfileDisplay("admin_user", "システム管理者", "https://example.com/admin.jpg", true);

// ケース 5: undefined を使って特定の位置のデフォルト値を維持
createUserProfileDisplay("test_user", "テストユーザー", undefined, false);

このように、デフォルト引数を活用すれば username だけの指定でも、他の項目が適切に補完されたプロフィールを生成できます。

4.2 例 2:描画ツールの設定関数

キャンバス上に図形を描画する関数を考えます。座標は必須ですが、色や線の太さはオプションにできます。

/**
 * 仮のキャンバスに図形を描画する
 * @param {number} x - X座標(必須)
 * @param {number} y - Y座標(必須)
 * @param {string} shapeType - 図形の種類。デフォルトは "円"
 * @param {string} color - 線の色。デフォルトは "黒"
 * @param {number} lineWidth - 線の太さ。デフォルトは 1
 * @param {boolean} fill - 塗りつぶしの有無。デフォルトは false
 */
function drawShape(x, y, shapeType = "円", color = "黒", lineWidth = 1, fill = false) {
  console.log(`位置 (${x}, ${y}) に ${shapeType} を描画:`);
  console.log(`  - 色: ${color}`);
  console.log(`  - 線幅: ${lineWidth}`);
  console.log(`  - 塗りつぶし: ${fill ? 'はい' : 'いいえ'}\n`);
}

// ケース 1: 最小限の指定 - (10, 20) にデフォルト設定で描画
drawShape(10, 20);

// ケース 2: 赤い長方形を太い線で描画
drawShape(50, 60, "長方形", "赤", 3);

// ケース 3: 塗りつぶされた青い正方形
drawShape(100, 120, "正方形", "青", 2, true);

// ケース 4: 線の太さだけ変えたい場合(間を undefined でスキップ)
drawShape(150, 180, undefined, undefined, 5);

この例は、多数の設定パラメータを持つ API 設計における一般的なパターンです。途中のデフォルト値を維持しつつ後ろの引数を指定したい場合は、undefined を明示的に渡す必要があります。これもまた、デフォルト引数を最後に配置すべき重要な理由の一つです。