ES2015で加わった、関数に関する仕様について。
引数のデフォルト値
関数の定義時に、引数のデフォルト値を指定できるようになった。
関数を呼び出す際に引数が渡された場合はそれを、渡されなかった場合はデフォルト値を、使用する。
function addFunc(x = 1, y = 2){ console.log(x + y); }; addFunc(); // 3 addFunc(4, 5); // 9 addFunc(4); // 6
rest parameters
rest parameters
は、残余引数とも呼ばれる。
これは、不特定多数の引数を受け取るための構文。受け取った引数は配列になる。
引数の頭に...
を付けると、その引数は残余引数になる。
function myFunc(...para){ console.log(para); }; myFunc(1); // [ 1 ] myFunc(1,2,3); // [ 1, 2, 3 ] myFunc('a','b'); // [ 'a', 'b' ] function addFunc(...para){ let sum = 0; for(let i=0; i < para.length; i++){ sum += para[i]; } console.log(sum); }; addFunc(1,2,3,4,5); // 15 addFunc(11,22,33); // 66
arguments
に似ているが、arguments
と違って残余引数は配列である。
そのため、配列のメソッドを使用することが可能。
また、arguments
は渡された全ての引数を含むが、残余引数は対象となっている引数のみを含む。
function myFunc(a, ...b){ console.log( arguments[0] ); console.log( b[0] ); console.log( 'push' in arguments ); console.log( 'push' in b ); }; myFunc('hoge', 'fuga'); // hoge // fuga // false // true
lengthでの扱い
全ての関数は、length
というプロパティを持つ。
これは、その関数に定義されている引数の数を示す。実際に渡された数ではなく、定義時の数である。
残余引数は、length
ではどのように扱われるのか。
function myFunc(a,b,c){ console.log(myFunc.length); }; function myFunc2(a, ...rest){ console.log(myFunc2.length); }; function myFunc3(a, b, ...rest){ console.log(myFunc3.length); }; function myFunc4(...rest){ console.log(myFunc4.length); }; myFunc(); // 3 myFunc2(); // 1 myFunc3(); // 2 myFunc4(); // 0
残余引数の数はカウントされないようだ。
そのため、残余引数しかないmyFunc4
では、length
は0
になっている。
アロー関数
ES2015で、新しい関数リテラルの書き方が加わった。
この書き方は、アロー関数と呼ばれている。
function(){}; // 従来の記法 () => {}; // アロー関数
省略記法
アロー関数では様々な省略記法が用意されている。
引数が1つのときは、()
を省略できる。
let myFunc = x => { console.log('受け取った引数は '+x); }; myFunc('hoge'); // 受け取った引数は hoge
return
を返すだけの場合は、return
と{}
を省略できる。
但し、オブジェクトリテラルを返す場合は、()
で囲まないと、正しく認識されない。
let myFunc3 = (x,y) => x + y; console.log(myFunc3(1,2)); // 3 let myFunc4 = (x,y) => [x,y]; console.log(myFunc4(1,2)); // [ 1, 2 ] let myFunc5 = (x,y) => {x:y}; console.log(myFunc5(1,2)); // undefined let myFunc6 = (x,y) => ({x:y}); console.log(myFunc6(1,2)); // { x: 2 }
即時関数
// SyntaxError () => { console.log('hoge'); }(); // SyntaxError ( () => { console.log('hoge'); }() ); // hoge ( () => { console.log('hoge'); } )();
即時関数の記法が厳密に決まっており、3番めの記法でないとエラーになる。
argumentsの挙動
アロー関数においては、arguments
は存在はするのだが、従来とは異なる挙動をする。
そのため、アロー関数で可変長の引数を用いる場合は、arguments
ではなく、先述の残余引数で対応する。
thisの整理
アロー関数では、this
の挙動も従来の関数とは異なる。
それについて述べる前にまず、これまでのthis
の挙動がどのようなものであったかを、整理しておく。
従来のthis
の最大の特徴は、それが何を指し示すかは文脈によって異なる、ということである。
this
は様々な文脈で呼び出され、その度に、その内容は変化する。
this
やapply()
の詳細は、下記を参照。
this(), call(), apply(), bind()
自身を呼び出しているオブジェクトを指す
this
は、グローバルスコープで呼び出された場合はグローバルオブジェクトを指し、オブジェクトのメソッドのなかで呼びされた場合はそのオブジェクトを指す。
プロトタイプに配置されているメソッドにおいても同様で、そのメソッドにアクセスしたオブジェクト(インスタンス)が、this
になる。
コンストラクタでの使用
コンストラクタとして関数を使った場合、つまりnew
演算子でインスタンスを作った場合は、コンストラクタのなかにあるthis
は、生成されるインスタンスのことを指す。
call,apply,bind
これらのメソッドを使うことで、this
となるオブジェクトを指定することが出来る。
入れ子になっている関数
これはほとんどバグと言っていい仕様だが、関数のなかに関数を入れると、内側の関数におけるthis
は、なぜかグローバルオブジェクトを指す。
global.prop = 'これはグローバルプロパティです。'; let obj = { prop: 'これはobjのプロパティです。', func: function(){ console.log(this.prop); // これはobjのプロパティです。 (function(){ console.log(this.prop); // これはグローバルプロパティです。 })(); } }; obj.func();
文脈への依存
this
の使われ方は、上記のようなパターンに分類される。そしてどのようにthis
を呼び出すかで、this
の内容が変わる。
繰り返しになるが、文脈によって内容が変わる、というのが従来のthis
の最大の特徴である。
JavaScriptでは例えば、変数のスコープは定義時に決定し、呼び出す文脈には影響を受けない。だがthis
においては、定義時には内容が決まらず、文脈によって決まる。
アロー関数におけるthis
アロー関数のなかのthis
は、文脈に依存せず、定義時にその中身が決まる。
これが、アロー関数最大の特徴である。
具体的には、定義しているスコープのthis
を引き継ぐことになる。
これは特に、先述の関数の入れ子において意味を持つ。
アロー関数は、入れ子にしてもthis
が切り替わらない。
global.prop = 'これはグローバルプロパティです。'; let obj = { prop: 'これはobjのプロパティです。', func: function(){ console.log(this.prop); // これはobjのプロパティです。 (function(){ console.log(this === global); // true console.log(this.prop);// これはグローバルプロパティです。 })(); (()=>{ console.log(this.prop); // これはobjのプロパティです。 })(); } }; obj.func(); // アロー関数では関数が入れ子になってもthisが変わらないことが分かる。
また、定義時にthis
が決まるという性質上、コンストラクタとしては使えない。
new
演算子を使うと、エラーになる。
let Func = (x) => { this.x = x; }; let ins = new Func(1); // TypeError: Func is not a constructor
call
、apply
、bind
を使っても、this
の中身は変わらない。
ただ、これらのメソッドを使ってもエラーにはならない。strictモードでも同様の挙動。
let normarFunc = function(){ console.log(this); }; let arrowFunc = ()=>{ console.log(this); }; let obj = { prop: 'prop' }; normarFunc.call(obj); // { prop: 'prop' } arrowFunc.call(obj); // グローバルオブジェクト
let normarFunc = function(){ console.log(this); }; let arrowFunc = ()=>{ console.log(this); }; let obj = { prop: 'prop' }; let bindFunc = normarFunc.bind(obj); bindFunc(); // { prop: 'prop' } bindFunc = arrowFunc.bind(obj); bindFunc(); // グローバルオブジェクト