JavaScript は, 古い仕様の上に新しい仕様を積み重ねているため、仕様がだいぶ混乱している。過去のコードの互換性のため、今さら変更できない。
このページは、関数呼出しの引数に注目します。新しい仕様である可変長引数、キーワード引数。
伝統的な挙動
関数側で値を受取る変数を「仮引数」, 呼出すときの値を「実引数」という。
- 仮引数 parameter
- 関数定義の冒頭で書かれる変数
- 実引数 argument
- 関数を実際に使用するときに, その関数に引き渡される値
JavaScript の古い仕様で特徴的なのは, function call のところ。
構文は, 次のようになっている (5.1 edition; 2011年)。ここは変哲もない。
- CallExpression
- MemberExpression Arguments
- Arguments
(
ArgumentListopt )
- ArgumentList
- AssignmentExpression |
ArgumentList ,
AssignmentExpression
関数呼出しの挙動は, 次のようになっている。JavaScript では, 関数もオブジェクトなので、プロパティを持つ。
- 実引数によってリストを生成する
this
と 1. の実引数リストとで, 関数オブジェクトの [[Call]]
内部プロパティを呼び出す。
- 実引数のほうが仮引数より少ないとき, 足らない引数には
undefined
が入る (10.5 Declaration Binding Instantiation)。
実引数のほうが多くても少なくてもエラーにならない。マジか~, そこはエラーにしてほしい・・・
実引数は, 関数のなかでは暗黙に, ローカル変数 arguments
に格納される。
JavaScript
- function s(a, b) {
-
- console.log(a, b, arguments);
- }
- s(1);
- s(1, 2, 3);
モダンな挙動
ECMAScript 2015 (ES2015; ES6) は 5.1 edition までと別物です。
- アロー関数 Arrow Function
- 関数の可変長パラメータ Rest Parameter
- 関数のデフォルトパラメータ Default Parameter
- 分割代入 Destructuring Assignment によるキーワード引数
- スプレッド構文 Spread Syntax による配列展開 (関数呼出し側)
古い構文に比べると、かなり首尾一貫した挙動になっています。アロー関数だけは, ソースコードを前から読むだけでは構文解析できない、省略しすぎ感ある。
1) アロー関数
アロー関数は, 新しく導入されたため, 古い仕組みの一部が変更になっている。暗黙のローカル変数 arguments
はエラーになる (未定義)。後述のレストパラメータを使えばよい。
this
の束縛はレキシカルスコープになる。function
はダイナミックスコープ。
JavaScript
-
- const w = (a1) => {
-
- console.log(a1);
- }
- w(1, 2, 3);
2) 可変長引数, 3) デフォルト引数
普通の仮引数の後ろにスプレッド構文 ...variable
を書けば、rest parameter として残りの実引数を配列として得ることができる (可変長引数)。
古い arguments
の代わりになる。arguments
と異なり, 仮引数と明示的に対応しない実引数だけが得られる。
それぞれの仮引数にデフォルト値を書けるようになった。デフォルト値は、実引数が undefined
のときに使われる。個数が足らないときではないことに注意。なので、デフォルト値を持つ仮引数の後ろにさらに仮引数を書いてもよい。
JavaScript
- function s(...args) {
- console.log(args);
- }
- s(1, 2);
- s({a: 1, b:'foo'});
-
- function t(a, b, ...rest) {
- console.log(rest);
- }
- t(1);
- t('a', 'b', 'c');
-
- function u(a = 'str', b) {
- console.log(a, b);
- }
- u();
- u({b: 'hoge'});
- u(undefined, 10);
デフォルト引数とレストパラメータの組み合わせも問題ない.
JavaScript
-
- const x = (a1, a2 = 'foo', ...args) => {
- console.log(a1, a2, args);
- }
- x(1);
- x(1, 'bar', 3, 365);
4) キーワード引数
分割代入構文を使って, キーワード引数を実現できる。
JavaScript
-
- function f({name, age = -100}) {
- console.log(name, age);
- }
-
- f({name:'Sato'});
- let obj = {name:'Yamada', age:20}
- f(obj);
-
-
- function g(a, b, {name, age}) {
- console.log(a, b, name, age);
- }
- g(1, 2, {name:'Taro'});
- g('Sato', 25, {});
-
-
-
- function h(x, [a, b]) {
- console.log(x, a, b);
- }
- h(1, [0, 2, 4]);
レストパラメータと組み合わせることもできる。レストパラメータは配列として受け取るので、それを分割代入で展開する。
分割代入のなかで、もう一度レストパラメータが使える。
JavaScript
-
- const y = function(a1, ...[r1, r2, ...rest]) {
- console.log(a1, r1, r2, rest);
- }
- y(1, 21, 22, 101, 102);
関数呼出しでの配列展開
今度は逆に, 関数呼出し時に引数を展開する。apply()
で配列を実引数として展開できる。これと同じ動作。
JavaScript
- function q(x, y, z) {
- console.log(x, y, z);
- }
- q.apply(null, [2, 4, 8]);
-
- let args = [3, 9, 81]
- q(...args)