【JS】クロージャーについて
JavaScriptには、クロージャーという関数の概念があります。
少し難しいので、今回はなるべくわかりやすくこのクロージャーについて解説していこうと思います。
クロージャーとは
クロージャーとは簡単に言うと、
「親のスコープにある変数への参照を保持できるという性質を使って関数の中で関数を実行すること」です。
具体例は以下のようになります。
const x = 10; // A
function printX() {
// この識別子`x`は常にAの変数`x`を参照する
const y = 20;
console.log(x); // => 10
}
function run() {
const x = 20; // B
printX(); // 常に10が出力される
console.log(y) // 参照できないのでエラーになる
}
run();
先ほど、「親のスコープにある変数への参照を保持できる」と説明しました。
ここで言う、"親"というのは"生みの親"のことを指します。
なので、run関数においてはprintXの生みの親であるAの方の変数xを参照しているわけです。
そして、自身にも親にもない変数のyを参照しようとしているので、エラーになります。
どういう仕組みか
JavaScirptにおいては、静的スコープというのを採用しています。
先ほどの例で、printX関数に書かれたxという変数は、run関数の実行とは関係なく静的にAの変数xを参照することが決定されます。
このように、どの識別子がどの変数を参照しているかを静的に決定する性質を、静的スコープと呼びます。
また、参照されなくなったデータはガベージコレクションにより解放されますが、外から参照されいてる限りデータは開放されません。
なので、以下のような状態を持った関数も定義することができます。
const createCounter = () => {
let privateCount = 0;
return () => {
privateCount++;
return `${privateCount}回目`;
};
};
const counter = createCounter();
console.log(counter()); // => "1回目"
console.log(counter()); // => "2回目"
const counter2 = createCounter();
console.log(counter2()); // => "1回目"
もう少し噛み砕いて説明します。
- 外からprivateCountが参照されているので、メモリが開放されずに状態を保ててる
- そして、静的スコープによる性質のおかげで関数ごとにそれぞれ状態を保てる
ということになります。
どんな時に使うのか
このクロージャーですが、主な使い道としては、以下の4つになります。
- 関数に状態を持たせる手段として
- 外から参照できない変数を定義する手段として
- グローバル変数を減らす手段として
- 高階関数の一部分として
まず、クロージャーで定義することで、先ほどの例のようにグローバルな変数を持つことなく関数内の状態を管理できます。
また、以下のような関数を返す関数の高階関数として利用することができます。
function greaterThan(n) {
return function(m) {
return m > n;
};
}
// 5より大きな値かを判定する関数を作成する
const greaterThan5 = greaterThan(5);
console.log(greaterThan5(4)); // => false
console.log(greaterThan5(6)); // => true
この書き方をカーリー化や関数の部分適用と言います。
ちなみに、クロージャの定義はサイトごとに違ったことを言っていたりするので、
「こういう書き方をすればこういう性質を持った関数が作れるのね」
ってことだけ覚えていただければOKかと。
おわりに
JavaScriptの概念であるクロージャについて解説してきました。
そこまで使う機会は多くないかも知れませんが、知っておくと便利なので記憶の片隅に置いていただければと。
最後に宣伝です。
0からエンジニアになるためのノウハウをブログで発信しています。
また、YouTubeでの動画解説も始めました。
YouTubeのvideoIDが不正ですインスタの発信も細々とやっています。
興味がある方は、ぜひリンクをクリックして確認してみてください!
おわり
Discussion
こんにちは。2つ目のスニペットの最終行の
console.log(counter()); // => "1回目"
は、正しくは
console.log(counter2()); // => "1回目"
ではないでしょうか。
ご確認いただけると幸いです。
コメントありがとうございます!
まさにその通りでございます。
修正しておきました🙇♂️