function
関数はファーストクラス
JavaScriptの関数は、数値、文字列、配列などと同じように扱う事ができる。すなわち
- 変数に代入できる
- 他の関数の引数として渡せる
- 他の関数の戻り値できる
- 実行時に動的に生成できる
- 匿名リテラルとして表現可能である
このような性質を持つオブジェクトはファーストクラス(第一級)オブジェクトと呼ばれる。(ファーストクラスオブジェクトのオブジェクトとはプログラミングにおいて識別子で参照される操作対象、メモリ上の値といった意味で、オブジェクト指向のオブジェクトではない)
/* 関数を変数に代入できる */ function hoge() { console.log('ほげ'); } const f = hoge; f(); // => ほげ // 関数はnameプロパティを持つ、自分自身の関数名である console.log(hoge.name); // => hoge // 変数に入れてもnameプロパティは変わらない console.log(f.name); // => hoge
関数にプロパティを作成できる(ただし作成する意味はあまりない)
/* 関数にプロパティを作成する。*/ function fuga() { // arguments.calleeは自分自身(= fuga)を参照する値 console.log(arguments.callee.count); } fuga.count = 100; fuga(); // => 100
関数式と関数宣言
これは普通の関数宣言
function hoge() { console.log('ほげ'); }
関数式は、式の中で関数を作成する方法。
const f = function hoge() { console.log('ほげ') }
関数宣言との違い
- 巻き上げが行われない
- 無名の関数を作れる(というか無名の関数を作る方が普通)
無名関数
名前を省略すると無名関数になる。関数式を作る場合は無名関数とする方が一般的。
const f = function() {}
巻き上げ
関数宣言の場合、関数の定義より前に呼び出してもエラーにならない
f(); // これはOK function f() {}
関数式の場合、関数の定義より前に呼び出すとエラーになる
f(); // エラー const f = function() {}
関数式の用途
Array#forEachなど、関数を引数として受け取る関数や関数を戻り値とする関数で使うことが多い
const arr = [1,2,3].map(function(i) { return i * 2; }); console.log(arr); // => [2,4,6]
現在このような用途では、アロー関数式の使用が推奨されている
const arr = [1,2,3].map((i) => i * 2); console.log(arr); // => [2,4,6]
function x(i) { return (j) => i * j; } x(2)(3) // => 6
メソッド
JavaScriptのメソッドはオブジェクトのプロパティとなった関数。メソッドはthisを通して自分自身の所属するオブジェクトにアクセスできる。
const person = { name: 'Nyan' } obj.greet = function() { // thisは自分自身が所属するオブジェクトを指す console.log(`Hello ${this.name} !`) } obj.greet(); // => Hello Nyan !
メソッドとして定義されてない関数のthisはグローバルオブジェクトとなる。
function g() { console.log(this); } g(); // ブラウザであればwindowオブジェクト, Node.jsならばglobalオブジェクト
アロー関数式
関数式の簡潔な代替構文。
[1,2,3].forEach((i) => { console.log(i * 2); // => 2, 4, 6 });
しかし多少の機能の違いもある。
- メソッドにできない(this、arguments、superへのバインドがない)
- コンストラクタにできない
- ジェネレータにできない
この性質の違いによって、大抵のケースではアロー関数式を使う方が良い結果となる。例えばメソッドの中で関数式を書くと意図通りに動作しないが、アロー関数ならば意図通りに動作する。
const obj ={ rate: 2, f() { // 関数式のthisはobjではなく、グローバルオブジェクトにバインドされてしまう [1,2,3].forEach(function(i) => { console.log(i * this.rate); }); }, g() { // 関数式ではthisを別の変数に避難させるテクニックが必要 // 意図通りになるが、分かりにくく煩雑 const self = this; [1,2,3].forEach(function(i) => { console.log(i * self.rate); }); } a() { // アロー関数はthisへのバインドがなく、この環境のthisが引き継がれる // 直感的、かつシンプルに記述できる [1,2,3].forEach((i) => { console.log(i * this.rate); }); } } obj.f(); // [NaN, NaN, NaN] obj,g(); // [2, 4, 6] obj.a(); // [2, 4, 6]
高階関数
関数を引数にする関数や、関数を戻り値にする関数を高階関数という。高階関数はJavaScriptの用語ではないが、JavaScriptでは空気のように多用される。
var array1 = [3,5,4,1,2]; array1.sort((a, b) => a - b ); // 降順ソート var array2 = [4,3,2,5,1]; array2.sort((a, b) => b - a); // 昇順ソート
Javaのように関数をオブジェクトとして利用することができない(難しい)言語ではストラテジーパターンで代用されています。Javaで配列をソートする場合、Comparatorインターフェースの実装クラスを作る必要があり面倒ですが、スクリプト言語は関数やコードブロックを渡すだけで良く、簡潔に記述できます。
クロージャ
自分自身が定義された環境を参照する関数をクロージャという。
function genCalcTaxRate() { const rate = 1.1; return (price) => price * rate; } const calcTaxRate = genCalcTaxRate(); console.log(calcTaxRate(100)); // => 110
genCalcTaxRateは関数を返す関数である。本来、変数rateはスタック領域に積まれる値であり、genCalcTaxRate関数が終了するとともに失われるはずである。しかし関数の中でアロー関数が定義され、そのアロー関数が変数rateを参照しており、なおかつ、そのアロー関数がreturnされている。こうなるとgenCalcTaxRateが終了しても変数rateはアロー関数に保持されるため失われない。
JSではクロージャも空気のように使われているため意識することは少ない