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

関数宣言と関数式 — 引数と return

JavaScript の関数を基礎から学びます。function 宣言による関数の作り方、引数と return の受け渡し、関数を値として変数に入れる関数式、return を書き忘れたときの undefined を扱います。

税込価格を出す式を一覧・カート・注文確認の 3 か所にそのまま書いていると、税率が変わったときに 3 か所すべてを直すことになり、直し漏れた画面だけ古い金額が残ります。

この記事では、処理に名前を付けて呼び出す function 宣言と、関数を値として変数に入れる関数式、return が返す値を扱います。

処理に名前を付けて呼ぶ — function 宣言と引数

商品ごとの税込価格を出す処理は、単価が違うだけで計算の中身は毎回同じです。違うのは渡す単価だけなので、計算そのものに名前を付けておけば、一覧でもカートでも「その名前を呼ぶ」だけで済みます。

関数(処理をひとまとまりにして名前を付け、何度でも呼び出せるようにしたもの)は function 名前(引数) { 本体 } の形で宣言します。丸括弧に書く引数は呼び出しごとに値を受け取る変数で、return に書いた値(戻り値)が呼び出した側へ戻ります。

// 税込価格を出す処理に taxIncluded という名前を付ける
function taxIncluded(price) {
  const tax = Math.floor(price * 0.1);
  return price + tax;             // 計算した値を呼び出し側へ返す
}

// 同じ関数を、違う単価で呼び出す
console.log(taxIncluded(1200));   // 1320
console.log(taxIncluded(980));    // 1078

// 値を渡し忘れると price には何も入らない
console.log(taxIncluded());       // NaN
同じ関数に 3 通りの呼び出しを通す
taxIncluded(price)の定義taxIncluded(1200)taxIncluded(980)taxIncluded()price は 1200tax は 120price は 980tax は 98price は undefinedtax は NaN1320 が戻る1078 が戻るNaN が戻る渡す渡す
price に入る値が変われば戻る値も変わります。値を渡さない呼び出しでは price が undefined になり、結果は NaN です

price に入るのは値そのものなので、1200 と直接書いても itemPrice のような変数に入れて渡しても、関数の中の動きは同じです。

研修の時間を「◯ 時間 ◯ 分」の形で表示します。lectureMinutes と practiceMinutes は宣言済みです。

① 分を受け取り「◯ 時間 ◯ 分」の文字列を返す関数を宣言してください。

② 講義の時間を「講義: ◯ 時間 ◯ 分」と表示してください。

③ 実習の時間を「実習: ◯ 時間 ◯ 分」と表示してください。

④ 2 つを合わせた時間を「合計: ◯ 時間 ◯ 分」と表示してください。

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

JavaScript / TypeScript エディタ

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

複数の引数を受け取る — 位置で決まる引数の対応

会員ランクやセールごとに割引率が変わると、単価だけを受け取る関数では足りません。割引率も一緒に渡せるようにすれば、計算の中身は 1 つのままで、率だけを呼び出し側で決められます。

引数はカンマで区切って並べます。呼び出し側の値は書いた位置の順に対応し、1 つ目の値が 1 つ目の引数へ、2 つ目の値が 2 つ目の引数へ入ります。名前で照合されるわけではないので、渡す順を取り違えても文法としては正しいままです。

// 単価と割引率を受け取って、割引後の価格を返す
function applyDiscount(price, rate) {
  return Math.floor(price * (1 - rate));
}

// 1 つ目の値が price に、2 つ目の値が rate に入る
console.log(applyDiscount(2400, 0.2));    // 1920
console.log(applyDiscount(2400, 0.05));   // 2280

// 順番を入れ替えて渡しても、そのまま計算が進む
console.log(applyDiscount(0.2, 2400));    // -480
引数は書いた位置で対応する
(2400, 0.2)と渡すprice は 2400rate は 0.22400 × 0.8を計算1920 が返る(0.2, 2400)と渡すprice は 0.2rate は 24000.2 × -2399を計算-480 が返る
同じ 2 つの値を、順番だけ変えて applyDiscount に渡した結果です。入れ替えてもエラーにはならず、計算だけが変わります

呼び出しの行には値しか書かれていないため、applyDiscount(0.2, 2400) を読むだけでは取り違えに気づけません。-480 のように本来ありえない値が返ったときは、呼び出しの値の並びを関数の定義と見比べます。

注文の支払額を 2 つの関数で求めます。unitPrice、quantity、shippingFee、couponAmount は宣言済みです。

① 単価と数量を受け取り、小計を返す関数を宣言してください。

② 小計・送料・値引き額の順に受け取り、支払額を返す関数を宣言してください。

③ ① の戻り値を ② に渡して「支払額: ◯ 円」と表示してください。

④ 送料を 0 にして「送料無料なら: ◯ 円」と表示してください。

JavaScript / TypeScript エディタ

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

関数を値として扱う — 関数式と変数への代入

送料の求め方は、通常便と翌日便で式そのものが違います。どちらを使うかを呼び出しのたびに書き分けるのではなく、使う関数そのものを 1 つの変数に入れておければ、呼び出す側はその変数を呼ぶだけで済みます。

関数式(関数を値として変数に代入する書き方)は const 名前 = function (引数) { 本体 }; と書く代入文です。function 宣言がコードを上から実行する前に準備されるのに対して、関数式の変数は代入の行が実行されて初めて関数を受け取ります(仕組みはスコープとホイスティングの記事で扱います)。

// function 宣言は、宣言より前の行からでも呼び出せる
console.log(expressShipping(1.5));       // 900

function expressShipping(weight) {
  return weight <= 2 ? 900 : 1300;
}

// 関数式: 関数を値として変数に代入する
const normalShipping = function (weight) {
  return weight <= 2 ? 500 : 800;
};

// 括弧を付けずに書けば、関数そのものを別の変数へ入れられる
const feeRule = normalShipping;
console.log(feeRule(3));                 // 800
console.log(typeof feeRule);             // function
console.log(typeof normalShipping(3));   // number
書いた位置より前の行から呼べるか
function 宣言で書く宣言より前の行で呼ぶ実行する前に準備されている900 が返る関数式で変数に入れる代入より前の行で呼ぶ変数はまだ関数を受け取っていないReferenceErrorで止まる
代入より前の行で呼ぶと ReferenceError で止まります。関数式に入れた関数は、代入の行を通るまで呼び出せません

表示されるエラーの全文は ReferenceError: Cannot access 'normalShipping' before initialization です。一方、括弧を付けずに代入すれば、関数そのものが別の変数にも入ります。

括弧を付けたときと付けないとき
feeRule =normalShipping関数そのものが入るtypeof はfunctionfeeRule(3) で後から呼べるfeeRule =normalShipping(3)本体が実行され800 が入るtypeof はnumberfeeRule(3) はTypeError
括弧が無い書き方では関数そのものが、括弧を付けた書き方では実行した結果が変数に入ります。後から呼びたい関数に括弧を付けると、残るのは戻り値だけです

同名の関数を宣言すると後の本体が使われます

function 宣言は実行前に準備されるため、同じ名前で 2 回宣言すると、1 つ目の直後の行で呼んでも後に書いた本体が使われます。実行は止まらないので、離れた場所の同名の関数に気づきにくくなります。const の関数式なら、2 回目の宣言が SyntaxError になり実行が始まりません。

決済手数料の計算を会員の種類で切り替えます。amount と isPremium は宣言済みです。

① 3% を返す standardFee と 1% を返す premiumFee を関数式で書いてください(切り捨て)。

② isPremium で片方を選んで applyFee に入れてください。

③ applyFee を呼んで「手数料: ◯ 円」と表示してください。

④ standardFee を直接呼んで「一般会員なら: ◯ 円」と表示してください。

JavaScript / TypeScript エディタ

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

戻り値が無いとどうなるか — return の書き忘れ

関数の中で console.log を書くと、呼び出したときに画面へ文言が出ます。表示は出ているので動いているように見えますが、その戻り値を変数に受け取って使うと、思っていた文字列が入っていません。

return は値を返すと同時に、そこで関数を終わらせる文です。return を書かなかった関数の戻り値は undefined で、表示することと値を返すことは別の動作です。console.log は画面に出すだけで、呼び出し側には何も渡しません。

// return がある関数: 文言が呼び出し側へ戻る
function formatLabel(name, stock) {
  return `${name}(残り ${stock} 点)`;
  console.log("この行は届かない");   // return より後なので実行されない
}

// return が無い関数: 表示は出るが、戻り値は undefined
function printLabel(name, stock) {
  console.log(`${name}(残り ${stock} 点)`);
}

const label = formatLabel("デスクライト", 3);
console.log(label);      // デスクライト(残り 3 点)

const printed = printLabel("USB ハブ", 5);   // USB ハブ(残り 5 点)
console.log(printed);    // undefined
関数の書き方呼び出したときの動き呼び出し側に残るもの
return で文言を返す文言が呼び出し側へ戻るデスクライト(残り 3 点)
console.log だけを書く画面には出るが何も返さないundefined
return の後に console.logreturn の後の行は実行されない文言はそのまま戻る

呼び出し側で値を使う予定がある関数では、表示ではなく return で値を渡します。

return の後で改行すると値が返りません

return の直後で改行して次の行に返す値を書くと、return の行で文が終わったとみなされ、戻り値は undefined になります。長いテンプレートリテラルを読みやすく折り返したときに起きやすい誤りです。返す値は return と同じ行から書き始めます。

送料を足して支払額を出します。orderTotal、largeOrderTotal、送料を返すはずの shippingFee は宣言済みです。

① orderTotal に送料を足した額を「支払額: ◯ 円」と表示してください。

② largeOrderTotal でも同じ形で表示してください。

③ どの金額でも送料を数値で返す shippingFeeFixed を宣言してください。

④ ① を shippingFeeFixed で表示し直してください。

JavaScript / TypeScript エディタ

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

理解度チェック

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

Q1function double(n) { n * 2; } を宣言して console.log(double(4)); を実行すると何が表示されますか?

Q2function fee(price, rate) { return price * rate; }fee(0.2, 1000) と呼ぶと、price には何が入りますか?

Q3const calc = function (n) { return n + 1; }; を、この代入より前の行で calc(1) と呼ぶとどうなりますか?