クロージャについてあまり使うことがなく知識が曖昧なのでアウトプットしました
クロージャは、組み合わされた(囲まれた)関数と、その周囲の状態(レキシカル環境)への参照の組み合わせです。言い換えれば、クロージャは内側の関数から外側の関数スコープへのアクセスを提供します。JavaScript では、関数が作成されるたびにクロージャが作成されます。 By MDN(久しぶりにサイト見に行ったら俺の知っているMDNじゃなくなっていた...)
いつも通りわからないことをわからないことで説明され余計わからなくなるパターンです。
ただ「わかったときにこの説明がしっくりくる...」というパターンでもあります。
まずレキシカルスコープの理解が必要でした。
レキシカルスコープ
function init() {
var name = 'Mozilla'; // name は、init が作成するローカル変数
function displayName() { // displayName() は内部に閉じた関数
alert(name); // 親関数で宣言された変数を使用
}
displayName();
}
init();
関数内で指定された変数を探すときに inner => outer => outerのように探していきます。
例) 鍵(変数)をなくしたときにポケット(inner)から探します。次に机の上(outer)、次に部屋全体(outer)、次に家全体(outer)、次に家の外(outer)..........
上記のコードでは、displayName関数の中で関数内にname変数があるかまず確認します。なかったので一段階上のinit関数の中からnameを探します。ありました。
init()内の変数nameはinit()が実行されると消えて無くなります。
通常、関数呼び出しが終わった後、すべての変数のとともに、レキシカル環境はメモリから削除されます。これはそこへの参照が存在しないためです。他のJavaScriptオブジェクトと同様に、到達可能な間だけメモリに保持されます。
しかし、関数の終了後も依然として到達可能なネストされた関数がある場合、それはレキシカル環境への参照である [[Environment]] プロパティを持ちます。
この場合、レキシカル環境は関数の完了後も依然として到達可能なので、存在し続けます。
変数nameをどうにかして生かす方法があります。
以下のクロージャでは 引用文のこれはそこへの参照が存在しないためです
この言葉が肝になります。
クロージャ
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7 と表示される
console.log(add10(2)); // 12 と表示される
内部のレキシカル環境は外部のものへの 外部 参照を持っています。
コードが変数にアクセスしたいとき、最初に内部のレキシカル環境を探します。その次に外側を探し、チェーンの最後になるまで繰り返します。
クロージャではこのレキシカルスコープを使用して値を保持し続けるということをしています。
なので以下のように値を保持してcountを増減させるといったことができます。
function makeCounter() {
let count = 0;
return function() {
return count++;
};
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2
Reactのhooksとかこういった機能で成り立たせているのか?など思ったりしました。
実際に自分がクロージャを使うことを考えたときにカプセル化になるからいいんじゃないかと思ったのですが、デバッグできないというデメリットがあるらしいです。
以下のクロージャのクイズが面白かったので見てみてください。
参考
強くなりたい!!!!!!!