順番に読み進めながら学べます

デフォルト引数と可変長引数 — 既定値と rest の受け取り方

JavaScript の関数で、省いた引数に使われるデフォルト引数と、個数の決まらない引数を配列でまとめて受け取る rest 引数、設定をオブジェクトで渡す options パターンを扱います。

配送料や税率のように、呼び出しのたびに同じ値を渡している引数があります。省いて呼び出してもエラーは出ず、表示された文字列に undefined が混ざって初めて渡し忘れに気づきます。

この記事では、省いた引数に使われる値を決めるデフォルト引数と、個数の決まらない引数をまとめて受け取る可変長引数、設定を 1 つのオブジェクトで渡す options パターンを扱います。

引数を省いても呼べるようにする — デフォルト引数

会議室の予約を組み立てる関数で、利用時間はほとんどが 1 時間、レイアウトもほとんどがスクール形式だとします。それでも呼び出すたびに 3 つの引数をすべて書くと、変えたい値がどれなのか呼び出し側で埋もれます。

デフォルト引数(引数が渡されなかったときに使う値を、あらかじめ決めておく書き方)は、引数の並びに hours = 1 のように = を添えて書きます。既定値を付けた引数は、呼び出し側で省けます。

// 既定値は引数の並びに「= 値」を添えて書く
const reserveRoom = (roomName, hours = 1, layout = "スクール") => {
  return `${roomName} / ${hours} 時間 / ${layout}`;
};

// 3 番目を省くと、既定値の "スクール" が使われる
console.log(reserveRoom("A 会議室", 2));         // A 会議室 / 2 時間 / スクール

// 渡した値がある位置では、既定値は使われない
console.log(reserveRoom("A 会議室", 2, "対面")); // A 会議室 / 2 時間 / 対面

// 2 番目も省くと、既定値が 2 つとも使われる
console.log(reserveRoom("A 会議室"));            // A 会議室 / 1 時間 / スクール
渡した引数は左から順に入る
roomName にA 会議室 を渡す既定値は付いていないroomName はA 会議室hours に2 を渡す既定値 1 は使われないhours は 2layout は書いていない既定値の式がここで動くlayout はスクール
reserveRoom("A 会議室", 2) を、引数の位置ごとに追ったものです。既定値が付いていても、値が渡っていれば動きません

省けるのは右端の引数からなので、既定値を付ける引数は後ろに並べます。3 番目だけを変えたいときも、2 番目の位置には値を書く必要があります。

配送料を見積もります。boxCount と地域別の単価は宣言済みで、送料は単価 × 箱数です。

① 箱の数と地域を受け取り送料を返す関数を、1 箱・本州を既定値にして書いてください。

② 地域を省いて boxCount 箱ぶんを「本州: ◯ 円」と表示してください。

③ 地域に北海道を指定して「北海道: ◯ 円」と表示してください。

④ 引数を 1 つも渡さずに「既定: ◯ 円」と表示してください。

(正しく実行できれば解説が表示されます)

JavaScript / TypeScript エディタ

コードを実行してください

渡さなかった引数に入るもの — undefined と既定値

既定値を付けていない引数を省いても、その引数を使った計算や文字列の組み立てはそのまま進みます。止まらないぶん、どの呼び出しで渡し忘れたのかを後から探すことになります。

渡されなかった引数には undefined が入ります。既定値が使われるのは引数が undefined のときだけで、null0 や空文字列を渡した場合は、渡したその値が使われます。

const notifyStatus = (orderId, status = "受付済み") => `${orderId}: ${status}`;

// 省いたときと undefined を渡したときは、どちらも既定値になる
console.log(notifyStatus("A-1021"));             // A-1021: 受付済み
console.log(notifyStatus("A-1021", undefined));  // A-1021: 受付済み

// null は値が渡された扱いなので、既定値は使われない
console.log(notifyStatus("A-1021", null));       // A-1021: null

// 既定値を書いていない引数を省くと undefined が入る
const plainStatus = (orderId, status) => `${orderId}: ${status}`;
console.log(plainStatus("A-1021"));              // A-1021: undefined
省いたときと null を渡したとき
status を省いて呼ぶstatus はundefined= "受付済み"が動くA-1021:受付済みstatus にnull を渡すstatus はnull既定値は動かないA-1021:null
省いた status には undefined が入り、既定値の式が動きます。null は渡された値として扱われ、既定値は動きません

下の表は、notifyStatus の 2 番目に渡す値ごとに、status に入る値と表示を並べたものです。undefined を書いた行は省いた行と同じ結果になり、null の行は文字列を渡した行と同じく、渡した値がそのまま表示されます。

呼び出しstatus に入る値表示
notifyStatus("A-1021")既定値の 受付済みA-1021: 受付済み
notifyStatus("A-1021", "発送済み")発送済みA-1021: 発送済み
notifyStatus("A-1021", undefined)既定値の 受付済みA-1021: 受付済み
notifyStatus("A-1021", null)nullA-1021: null

null を渡すと既定値は使われません

null は「意図的に空である」ことを表す値で、引数に既定値を書いても置き換わりません。API の応答が null のときも既定値を使いたい場合は、引数側に既定値を置かず、関数の中で const shown = status ?? "受付済み"; のように ?? を使います。

在庫数を表示します。在庫 0・キー無し・null の商品を含む products は宣言済みです。

① 在庫数を受け取り「在庫: ◯」を返す stockLabel を、「確認中」を既定値にして書いてください。

② 全商品を「商品名 在庫: ◯」と表示してください。

③ null も「確認中」にする stockLabelSafe を、既定値を使わずに書いてください。

④ ケーブルと USB ハブを stockLabelSafe で表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

個数が決まらない引数をまとめる — rest 引数

商品に付けるタグや経費の明細のように、渡される値の個数が呼び出しごとに変わる処理があります。引数を 5 つ用意しても足りない呼び出しが出てきますし、使われなかった引数には毎回 undefined が入ります。

rest 引数(引数の並びの残りを 1 つの配列にまとめて受け取る書き方。可変長引数とも呼びます)は、引数の最後に ...labels と書きます。分割代入で残りをまとめた ... と同じ記号で、集める対象が引数の並びになったものです。置けるのは最後の 1 つだけで、後ろに引数を続けると SyntaxError になります。

// ...labels と書くと、受け取った引数がまとまって 1 つの配列になる
const buildTags = (...labels) => `${labels.length} 件: ${labels.join(" / ")}`;

console.log(buildTags("送料無料", "在庫あり"));  // 2 件: 送料無料 / 在庫あり
console.log(buildTags("新着"));                 // 1 件: 新着

// 逆に、呼び出しの括弧の中に ... を書くと、配列を引数の並びに戻せる
console.log(buildTags(...["新着", "限定"]));     // 2 件: 新着 / 限定

// 名前を付けて受け取る引数があるときは、rest を最後に置く
const buildNotice = (orderId, ...notes) => `${orderId}${notes.join("・")})`;

console.log(buildNotice("A-1021", "冷蔵", "時間指定"));  // A-1021(冷蔵・時間指定)
console.log(buildNotice("A-1022"));                      // A-1022()
並べた値がどの引数に入るか
1 つ目"A-1021"orderIdに入る2 つ目"冷蔵"notes[0]に入る3 つ目"時間指定"notes[1]に入る
1 つ目の値は orderId に、残りは順に notes の要素に入ります。rest は、名前を付けた引数の後ろの残りを受け取ります

rest 引数には既定値を付けられませんが、1 つも渡されなかったときの notesundefined ではなく空の配列です。逆に、手元に配列がある状態で 1 件ずつの引数として渡したいときは、呼び出しの括弧の中に ... を書きます。

出張でかかった交通費を合計します。trainFare、busFare、taxiFare は宣言済みです。

① 受け取った金額をすべて合計して返す sumExpenses を可変長引数で書いてください。

② 3 件を渡した合計を「合計: ◯ 円」と表示してください。

③ 1 件も渡さずに呼んだ結果を、同じ形で表示してください。

④ 費目名「交通費」と 3 件の交通費を受け取る summarize を書き、「交通費: ◯ 円(◯ 件)」と表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

名前を付けて渡す — options と分割代入

メールの送信のように設定項目が増えると、sendMail(to, subject, false, true) のような呼び出しになります。最後の falsetrue が何を指すのかは、関数の定義を開かないと読み取れません。

options パターン(複数の設定を 1 つのオブジェクトにまとめて渡す書き方)では、受け取る側で分割代入し、キーごとに既定値を付けます。設定ごと渡さない呼び出しに備えて、分割代入の後ろに = {} を添え、オブジェクトが来なかったときは空のオブジェクトを置きます。

// 引数が並ぶほど、呼び出し側では何を渡しているのか読み取れない
const sendMailOld = (to, subject, isUrgent, withPdf) =>
  `${to} / ${subject} / ${isUrgent} / ${withPdf}`;

console.log(sendMailOld("aya@example.com", "発送のお知らせ", false, true));
// aya@example.com / 発送のお知らせ / false / true

// options パターン: 1 つのオブジェクトにまとめ、受け取る側で分割代入する
const sendMail = (to, { subject = "お知らせ", isUrgent = false } = {}) =>
  `${to} / ${subject} / ${isUrgent}`;

console.log(sendMail("aya@example.com", { isUrgent: true }));  // aya@example.com / お知らせ / true
console.log(sendMail("aya@example.com"));                     // aya@example.com / お知らせ / false
書いたキーだけが上書きされる
{ isUrgent: true }を渡すisUrgent にtrue が届くisUrgent はtrue同じ呼び出しにsubject は無いsubject の既定値が動くsubject はお知らせ別の呼び出し2 番目を渡さない= {} が空の設定を置く2 つとも既定値
書いたキーだけが渡り、書かなかったキーは既定値のままです。オブジェクトごと省ける呼び出しに備えるのが、末尾の = {} です

引数の順番を覚える代わりにキー名で意味を示せるので、設定が増えても呼び出し側は読めます。キーは名前で対応するため、{ isUrgent: true, subject: "再送" } のように書く順番を入れ替えても、受け取る値は同じです。

= {} を省くと TypeError になります

{ subject = "お知らせ" } のように分割代入だけを書くと、2 番目を省いた呼び出しで TypeError: Cannot read properties of undefined (reading 'subject') が出ます。省かれた引数の undefined から、キーを取り出そうとするためです。

弁当の価格表示を、設定オブジェクトで切り替えます。lunchPrice は宣言済みです。

① 税抜価格と設定を受け取り「1058 円」の形の文字列を返す formatPrice を、設定は分割代入で受けて書いてください(税率 0.08・単位 円 が既定値)。

② 設定を渡さずに呼んだ結果を表示してください。

③ 税率 0.1・単位「円(店内)」を指定した結果を表示してください。

④ 税率に 0 を指定した結果を表示してください。

JavaScript / TypeScript エディタ

コードを実行してください
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1const addFee = (price, fee = 500) => price + fee;addFee(1000, undefined) は何を返しますか?

Q2const countNotes = (id, ...notes) => notes.length;countNotes("A-1021") と呼ぶと何が返りますか?

Q3const send = (to, { urgent = false } = {}) => urgent;send("aya@example.com") と呼ぶと何が返りますか?