Q1function double(n) { n * 2; } を宣言して console.log(double(4)); を実行すると何が表示されますか?
関数宣言と関数式 — 引数と 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
price に入る値が変われば戻る値も変わります。値を渡さない呼び出しでは price が undefined になり、結果は NaN です。price に入るのは値そのものなので、1200 と直接書いても itemPrice のような変数に入れて渡しても、関数の中の動きは同じです。
複数の引数を受け取る — 位置で決まる引数の対応
会員ランクやセールごとに割引率が変わると、単価だけを受け取る関数では足りません。割引率も一緒に渡せるようにすれば、計算の中身は 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
applyDiscount に渡した結果です。入れ替えてもエラーにはならず、計算だけが変わります。呼び出しの行には値しか書かれていないため、applyDiscount(0.2, 2400) を読むだけでは取り違えに気づけません。-480 のように本来ありえない値が返ったときは、呼び出しの値の並びを関数の定義と見比べます。
関数を値として扱う — 関数式と変数への代入
送料の求め方は、通常便と翌日便で式そのものが違います。どちらを使うかを呼び出しのたびに書き分けるのではなく、使う関数そのものを 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
ReferenceError で止まります。関数式に入れた関数は、代入の行を通るまで呼び出せません。表示されるエラーの全文は ReferenceError: Cannot access 'normalShipping' before initialization です。一方、括弧を付けずに代入すれば、関数そのものが別の変数にも入ります。
同名の関数を宣言すると後の本体が使われます
function 宣言は実行前に準備されるため、同じ名前で 2 回宣言すると、1 つ目の直後の行で呼んでも後に書いた本体が使われます。実行は止まらないので、離れた場所の同名の関数に気づきにくくなります。const の関数式なら、2 回目の宣言が SyntaxError になり実行が始まりません。
戻り値が無いとどうなるか — 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.log | return の後の行は実行されない | 文言はそのまま戻る |
呼び出し側で値を使う予定がある関数では、表示ではなく return で値を渡します。
return の後で改行すると値が返りません
return の直後で改行して次の行に返す値を書くと、return の行で文が終わったとみなされ、戻り値は undefined になります。長いテンプレートリテラルを読みやすく折り返したときに起きやすい誤りです。返す値は return と同じ行から書き始めます。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2function fee(price, rate) { return price * rate; } を fee(0.2, 1000) と呼ぶと、price には何が入りますか?
Q3const calc = function (n) { return n + 1; }; を、この代入より前の行で calc(1) と呼ぶとどうなりますか?