平凡なJavaScriptプログラマーとJavaScriptニンジャを分かつものは、関数型言語としてのJavaScriptを理解しているか否かである。
最も重要な点は、JavaScriptにおいて関数は一級オブジェクト(あるいは、一級市民)であるということである。関数は、その他のJavaScriptと同じように扱うことができる。変数に入れたり、リテラルで定義したり、関数の引数にしたりできる。
機能面の違い
関数がファーストクラスオブジェクトである
関数をファーストクラスオブジェクトとして扱うことは、関数型プログラミングのはじめの一歩でもある
メモ:本書の次は JavaScript関数型プログラミング を読もうと思います
コールバック関数
コールバック関数とは、関数を利用できるよう引数などの形で渡すこと。イベントハンドラなど、様々な場面で使用するテクニック。
関数をオブジェクトとして扱う
関数を通常のオブジェクトと同様に扱うことで、様々なことを実現できる。
関数を保持する
関数をコレクションの中に保持して、あとでまとめて呼び出すことができる。
単純な実装としては配列の中に保管することになるが、以下のように、関数にプロパティを設定することで重複登録を奉仕できる。
(ES2015のSet
を使うとより良い実装ができる)
const store = {
nextId: 1,
cache: {},
add: (fn) => {
if (!fn.id) {
fn.id = this.nextId++;
this.cache[fn.id] = fn;
return true;
}
}
};
function ninja() {}
assert(store.add(ninja), 'Function was safely added');
assert(!store.add(ninja), 'But it was only added once');
function assert(value, text) {
if (text) console.log(text);
return !!value;
}
自己メモ化関数
メモ化とは、前回呼び出し時に計算した値を記憶しておくことである。
function isPrime(value) {
if (!isPrime.answers) {
isPrime.answers = {};
}
if (isPrime.answers[value] !== undefined) {
return isPrime.answers[value];
}
let prime = value !== 1; // 1 is not a prime
for (let i = 2; i < value; i++) {
if (value % i === 0) {
prime = false;
break;
}
}
return isPrime.answers[value] = prime;
}
console.log(isPrime(5));
console.log(isPrime.answers[5]);
関数定義
JavaScriptにおける関数定義には以下の4つの方法がある。
// 関数宣言と関数式
function myFunc() { return 1; }
// アロー関数(ES2015)
myArg => myArg*2
// 関数コンストラクタ(ほとんど使わない)
new Function('a', 'b', 'return a + b')
// ジェネレーター関数(ES2015)
function* myGen() { yield 1; }
関数宣言と関数式
最も一般的な関数定義の方法は、関数宣言と関数式である。この2つはよく似ているが、区別すべきである。
関数宣言
関数宣言では、関数名が必須。
function myFunctionName(myFirstArg, mySecondArg) {
myStatement1;
myStatement2;
}
関数式
JavaScriptの関数は、他のオブジェクトと同様、リテラルで定義できる。
文の中に関数のリテラルが含まれる場合、関数式と呼ばれる。
関数式を使うと、通常の変数と同様、定義した場所以降で関数が利用可能になる。
これに対して、関数宣言は、スコープの冒頭で定義したものとみなされる(巻き上げ)。
// 関数式(基本形)
var myFunc = function() {};
// 関数式は引数にも書ける
myFunc(function() {
return function() {};
});
// 名前付き関数式
var f = function namedFunction() {};
f(); // OK
namedFunction(); // 未定義関数の呼び出しエラー
console.log(f.name); // 'namedFunction'
即時関数
関数を定義してすぐに呼び出すテクニックを即時呼び出し関数式(Immediately Invoked Function Expression, IIFE)、または即時関数(Immediate Function)という。
即時関数では、通常関数名を書くところに関数式を書いて、それを括弧で囲む。
// 通常の関数呼び出し
myFunctionName (3);
// 即時関数
(function(){}) (3);
// 括弧で囲まない場合、
// 名前のない関数宣言とみなされて構文エラーになる
function(){} (3);
// この書き方でもOK(全体を括弧で囲むことで、関数宣言ではなく関数式とみなされるようにする)
(function(){} (3));
// こういう書き方もある
+function(){}();
-function(){}();
!function(){}();
~function(){}();
アロー関数式
アロー関数式はES2015で追加された機能である。
JavaScriptでは関数式を多用するので、短く書く方法が導入された。
アロー関数式という呼び名は、 =>
演算子(アロー演算子)に由来している。
const values = [0, 3, 2, 5, 7, 4, 8, 1];
// 通常の関数式
values.sort(function(value1, value2) {
return value1 - value2;
});
// アロー関数式(functionキーワードを省略)
values.sort((value1, value2) => {
return value1 - value2;
});
// 関数本体が1つの式だけの場合は、そのまま式を書ける(式の値はreturnされる)
values.sort((value1, value2) => value1 - value2);
// 引数が1つだけの場合は引数の括弧も省略できる
const greet = name => `Greetings ${name}`;
// ただし、引数が1つもない場合には括弧は省略できない
const helloWorld = () => 'Hello, world!';
引数と関数のパラメータ
- パラメータ(parameter、仮引数)とは、関数定義の一部として定義する変数である
- 引数(argument、実引数)とは、関数呼び出し時に渡す値である
パラメータとは異なる数の引数を渡した場合、JavaScriptではエラーは発生しない。過剰なパラメータには割り当てられず、値が渡されなかったパラメータの値はundefined
になる。
Rest parameters(余り物パラメータ)
パラメータの定義で、n番目のパラメータの名前の前に ...
をつけると、このパラメータはRest parameters(余り物パラメータ)となる(※ES2015で導入された機能)。
このパラメータは、n番目以降の全ての引数を格納する配列になる。
function multiMax(first, ...remainingNumbers) {
const sorted = remainingNumbers.sort((a, b) => b - a);
return first * sorted[0];
}
console.log(multiMax(3, 1, 2, 3)); // 9
デフォルトパラメータ
// ES5までのデフォルトパラメータ
function performAction(ninja, action) {
action = typeof action === 'undefined' ? 'skulking' : action;
return ninja + ' ' + action;
}
// ES2015のデフォルトパラメータ
function performAction(ninja, action = 'skulking') {
return ninja + ' ' + action;
}
// デフォルトパラメータには任意の式を書ける(可読性が下がるので避けたほうがよい)
function performAction(ninja, action = 'skulking', message = ninja + ' ' + action) {
return message;
}