パラメータと引数の違い
この2つ、本当によく混同されます。パラメータ(仮引数) は関数を定義するときに書く名前、引数(実引数) は関数を呼び出すときに渡す値のこと。PythonをはじめJavaScript以外の言語でも考え方は同じです。
a と b が仮引数(parameters)で、2 と 3 が実引数(arguments)です。JavaScript は位置ベースで値を割り当てます。つまり最初の実引数は最初の仮引数に、次は次に…という具合です。細かい違いに見えますが、エラーメッセージや MDN でも使われる用語なので覚えておきましょう。
JavaScript は引数の数にゆるい
他の多くの言語と違い、JavaScript は引数の数が足りなくても多すぎても文句を言いません。足りない仮引数は undefined になり、余分な実引数は黙って無視されます。
最初の呼び出しでは Hello, Ada undefined と表示されます。last に値が渡されなかったので undefined になり、テンプレートリテラルはそれをそのまま文字列に埋め込んでしまいます。エラーも警告も出ません。この寛容さは便利なこともあれば、バグの温床になることもあります。だからこそ、デフォルト引数の出番というわけです。
デフォルト値を設定する
パラメータ名のうしろに = と値を書くだけです。呼び出し側がその引数を渡さなかったとき(あるいは undefined を渡したとき)に、デフォルト値が使われます:
3つの呼び出しはどれもちゃんと動きます。1番目と3番目はデフォルト値が使われ、2番目は渡した値がそのまま使われます。これはES6で導入された書き方で、2015年以前は関数の中で name = name || "friend" と書くしかなく、0 や "" のような falsy な値まで置き換わってしまうバグを抱えがちでした。
デフォルト値にはリテラルだけでなく、任意の式を指定できます。
この式は呼び出しのたびに評価されます。関数定義時に一度だけ評価されるわけではありません。なので、Python のようにミュータブルなデフォルト値でハマることはなく、毎回フレッシュな new Date() が得られます。
デフォルトが発動するのは undefined のときだけ
ここが多くの人がハマるポイントです。デフォルト値が適用されるのは引数が undefined のときだけで、falsy な値のときでも、null のときでも発動しません。
最後の呼び出しだけが "friend" を使います。null を明示的に渡すということは、「値は null です」と伝えているのと同じで、JavaScript はその言葉通りに受け取ります。空文字列や 0 も同様で、これらは「値がない」のではなく、れっきとした値として扱われます。
null を「引数なし」と同じように扱いたい場合は、自分で処理する必要があります。
?? 演算子(Nullish 合体演算子)は、null と undefined の両方を「値がない」とみなします。これについては後の章で詳しく扱います。
デフォルト値に前の引数を使う
引数は左から右へと順番に評価されるので、後ろのデフォルト値には、それより前に定義した引数を自由に参照できます。
引数を1つだけ渡すと立方体になり、2つ渡すと四角柱になります。宣言の順番には気をつけてください。まだ宣言されていないパラメータを参照することはできません。
function bad(a = b, b = 1) {
return a + b;
}
bad(); // ReferenceError: Cannot access 'b' before initialization
let で宣言した変数と同じで、宣言前に使うとエラーになります。
デフォルト引数と分割代入の組み合わせ
引数を分割代入しつつ、同時にデフォルト値も指定できます。これは「オプションオブジェクト」を受け取る関数でよく使われるパターンです。
デフォルトは2段構えになっています。内側の role = "member" や active = true は、プロパティが渡されなかったときに穴埋めしてくれる部分。外側の = {} は、呼び出し側が引数そのものを渡さなかった場合の受け皿です。これがないと、createUser() のように呼んだ瞬間に undefined を分割代入しようとしてエラーになります。
最初は見た目がごちゃっとして見えますが、このパターンはモダンな JavaScript のコードベースでは本当によく登場します。{ ... } = {} を見て「デフォルト値付きのオプション引数だな」とパッと認識できるようになれば、スラスラ読めるようになりますよ。
途中の引数だけスキップしてデフォルトを使う
JavaScript には Python のようなキーワード引数の仕組みはありません。途中の引数をスキップしてそこだけデフォルト値を使いたいときは、明示的に undefined を渡します。
prefix に undefined を渡せばデフォルト値が使われますが、見た目がよろしくありません。呼び出し側で undefined を何度も書いているなと感じたら、それはオプションオブジェクトに切り替えるべきサインです。
呼び出し側は上書きしたい項目だけ名前で指定できるので、順番もどうでもよくなります。
デフォルト引数は length にカウントされない
ほとんど気にすることはないのですが、たまにハマるポイントがあります。関数の length プロパティは、デフォルト値を持つ最初の引数より前の引数の数しか返しません。
関数を解析するタイプのライブラリ(テストツールやDIコンテナなど)は、この length を使って「必須引数の数」をカウントしています。こういうルールがあるんだな、と頭の片隅に入れておけば十分で、自分でそういうツールを作るのでなければ丸暗記する必要はありません。
次は Rest と Spread
デフォルト引数は、引数の顔ぶれが事前にわかっているケースで活躍します。一方で、「いくつ渡ってくるかわからない引数を受け取りたい」「受け取った引数をまとめて別の関数に渡したい」といった場面もありますよね。そこで登場するのが ...rest とスプレッド演算子です。次回はこのあたりを見ていきましょう。
よくある質問
デフォルト引数はどうやって設定するの?
関数定義のパラメータ名の後に = と初期値を書くだけです。例えば function greet(name = 'friend') { ... } のように書きます。呼び出し時に引数を省略した場合や undefined を渡した場合に、この初期値が使われます。ES6で追加された構文で、モダンなJavaScript環境ならどこでも動きます。
「パラメータ」と「引数」って何が違うの?
パラメータ(仮引数)は関数定義側の名前のこと。function add(a, b) なら a と b がパラメータです。一方、引数(実引数)は呼び出し時に実際に渡す値で、add(2, 3) の 2 と 3 が引数にあたります。エラーメッセージや公式ドキュメントを読むときに、この違いを押さえておくと理解が早くなります。
null を渡したときもデフォルト値が使われるの?
いいえ、使われません。デフォルト値が発動するのは引数が undefined のとき(または省略されたとき)だけです。null を明示的に渡すと、「値としてnullを渡している」とみなされるので、デフォルト値はスキップされます。nullとundefinedを同一視する言語から来た人がハマりやすいポイントです。
デフォルト値に他のパラメータを使ってもいい?
もちろんOKです。パラメータは左から右へ順に評価されるので、後ろのパラメータのデフォルト値に前のパラメータを使えます。例えば function box(width, height = width) のような書き方です。さらに function log(msg, time = Date.now()) のように関数呼び出しをデフォルト値にすることもでき、この式は呼び出しのたびに毎回評価されます。