JavaScriptでのクロージャとカリー化の概要
序章
JavaScriptでコードを書く場合、closure
という用語に出くわした可能性が非常に高くなります。これは、便利でありながら混乱を招く概念です。 しかし、閉鎖とは何ですか?
クロージャは、関数とそれが宣言された字句環境の組み合わせとして説明できます。
しかし、これは正確にはどういう意味ですか? 字句環境は、関数の作成時に関数のスコープ内のローカル変数で構成されます。 クロージャを使用すると、関数のすべてのローカル変数を、見つかった状態で参照できます。
これは基本的に、別の関数内に関数を定義することによって実現されます。関数内のこの関数は、技術的にはクロージャーです。 親関数が呼び出されるたびに、すべてのローカル変数の新しいコピーを保持する新しい実行コンテキストが作成されます。 これらのローカル変数は、グローバルに宣言された変数にリンクするか、親関数からクロージャーを返すことにより、グローバルスコープで参照できます。
基本的な例は、次のような形式になります。
function closuredFunc (){ function closure(){ // some logic } }
以下に示すように、いくつかのメソッドを返すクロージャーを持つことも可能です。
function closure(){ function first() { console.log('I was declared first')} function second() { console.log('I was declared second')} function third() { console.log('I was declared third')} return [first, second, third] }
これらの各メソッドを参照するために、クロージャーをグローバル変数に割り当てます。グローバル変数は、公開されたメソッドの配列を指します。 次に、各メソッドを一意の変数名に割り当てて、以下に示すようにグローバルスコープに含めることができます。 この時点で、それらを呼び出すことができます。
let f = closure() let one = f[0] let two = f[1] let three = f[2] one() // logs I was declared first two() // logs I was declared second three() // logs I was declared third
なぜクロージャを使用するのですか?
なぜ閉鎖をするのに苦労するのか疑問に思われるかもしれません。 まあ、クロージャーには多くの用途と利点があります。
ES6にクラスが導入される前は、クロージャはオブジェクト指向プログラミングで使用されるものと同様のクラスのようなプライバシーを作成する手段を提供し、プライベートメソッドをエミュレートできるようにしました。 これはmodule pattern
として知られており、名前空間の汚染を減らし、再利用性を高めて、保守が容易なコードを記述できます。
これが行われる場合を見てみましょう:
var makeCounter = function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } }; var counter1 = makeCounter(); var counter2 = makeCounter(); counter1.value(); // returns 0 counter1.increment(); // adds 1 counter1.increment(); // adds 1 counter1.value(); // returns 2 counter1.decrement(); //subtracts 1 counter1.value(); // returns 1 counter2.value(); // returns 0
上記の例では、関数makeCounter
を宣言しました。これは、privateCounter
やそれを操作する関数などのプライベート変数にアクセスできるパブリック関数です。 これは、makeCounterを独自の機能と変数が組み込まれたクラスとして作成する動作を模倣しています。 これは、counter1
とcounter2
の2つの異なるカウンターを作成したときにわかります。 各カウンターは互いに独立しており、異なるバージョンの変数を参照します。
クロージャを使用すると、関数を使用して、引数に特定の値を追加する他の関数を作成することもできます。 この場合、この動作を可能にする親関数は、本質的に他の関数を作成するため、function factory
と呼ばれます。
関数ファクトリを使用すると、Currying
と呼ばれる動作を実現できます。これについては、次のセクションで説明します。
カリー化とは
Currying
は、他の関数を即座に評価して返す関数のパターンです。 これは、Javascript関数が他の関数を返すことができる式であるという事実によって可能になります。
カレー関数は、内部関数を同時に定義してすぐに返すことにより、クロージャを連鎖させることによって構築されます。
カリー化の例は次のとおりです。
let greeting = function (a) { return function (b) { return a + ' ' + b } } let hello = greeting('Hello') let morning = greeting('Good morning') hello('Austin') // returns Hello Austin hello('Roy') // returns Hello Roy morning('Austin') // returns Good morning Austin morning('Roy') //returns Good Morning Roy
greeting
から作成された2つの関数(hello
およびmorning
)はそれぞれ、提供された入力を処理して挨拶文を生成する関数を返します。 彼らはまた、迎えられる人の名前である議論をします。
上記の場合、挨拶は関数ファクトリとしても使用され、そこからhelloとmorningの2つの関数が生成されます。
内部関数は、次のように最初の呼び出しの後に呼び出されることもあります。
greeting('Hello There')('General Kenobi') //returns Hello There General Kenobi
カリー化は関数型プログラミングの一部と見なされているため、カリー化された関数は、ES6および新しいバージョンのJavascriptの矢印関数構文を使用して簡単に記述でき、よりクリーンでエレガントなコードになります。
let greeting = (a) => (b) => a + ' ' + b greeting('Hello There')('General Kenobi') //returns Hello There General Kenobi
結論
JavascriptがES6にクラスを組み込んだため、クロージャーは一般的に使用されていない可能性がありますが、クリーンで再利用可能なコードを作成する場合は、クロージャーが引き続き使用されます。 クロージャとカリー化も、オブジェクト指向プログラミングのプライベートメソッドと同様の目的を本質的に果たす関数型プログラミングに関して理解するための重要な概念です。