前回の記事「【JavaScript】クロージャの概念を理解する」で紹介した「クロージャ」を適切に使用するには、メモリ管理やガベージコレクションについて理解しておく必要があります。
これらを理解せずにクロージャを多用すると
メモリリーク(=メモリがいっぱいになって処理が遅くなったり、クラッシュしたり)する可能性があります。
そこで本記事では、クロージャとあわせて知っておきたいメモリに関する基礎知識をまとめました。
✅ そもそもメモリはどういうものか
「メモリ」といっても、さまざまな種類がありますが
プログラムにおけるメモリはデータやプログラムを一時的に保管する場所のことです。
よく作業机に例えられます。
- 作業に必要な道具を机の上に広げて使う
- 作業が終わったら、不要なものは片付ける
というイメージです。
このように、プログラムにおいても
必要なデータ(=変数や関数など)をメモリ上に置き、使い終われば片付けます。
💡 メモリのライフサイクル
どのプログラミング言語も、メモリは以下の流れで使用されます。
- メモリ割り当て:変数や関数を定義したときに、必要なメモリが確保されます
- 使用:メモリに保持された変数や関数を参照する形で使用します
- 解放:必要なくなったらメモリから削除されます
このようなライフサイクルは、
言語によって自動で管理される場合もあれば、開発者が自分で管理しなければいけない場合もあるみたいです。
JavaScriptの場合は?
JavaScriptの場合、ライフサイクルのほぼ全ての工程は自動で行われます。
- 変数や関数を宣言したときに自動でメモリを確保
- 参照されている間はメモリに保持
- 不要になったデータは自動で削除(=メモリの解放)
メモリを自動で解放する仕組みが 「ガベージコレクション(GC)」 です。
プログラム内で参照されなくなったデータは、ガベージコレクションの対象となり
適切なタイミングで自動的にメモリから削除されます。
✅ クロージャとガベージコレクションの関係
ここで、クロージャ・レキシカルスコープ・ガベージコレクションの定義をおさらいします。
クロージャ:関数とその定義時のレキシカルスコープを一緒に保持し、関数の外からでもそれらを参照することができる仕組みのこと
レキシカルスコープ:関数が参照できるスコープは、その関数の定義時に決定されるという参照の使用のこと。
ガベージコレクション:不要と判断したデータをメモリから自動で削除する仕組みのこと
以下の例(クロージャのカプセル化)を用いて、クロージャとガベージコレクションの関係を解説します。
function createCounter() {
let count = 0;
function countUp () {
count += 1;
console.log(count);
};
return countUp;
}
let counter = createCounter(); // クロージャが生成される
counter(); // 実行結果:1
counter(); // 実行結果:2
counter(); // 実行結果:3
クロージャを生成すると、変数counter
は クロージャを参照します。
クロージャはcountUp関数
とそこから参照されているcount
の値(=レキシカルスコープ)をメモリに確保します。
⚠️ レキシカルスコープがメモリに残り続けることに注意
counter
がクロージャを参照し続けていると
count
の値はcountUp関数
から参照され続けます。
つまり、ガベージコレクションによって自動で削除されず
不要なデータが意図せずメモリに残り続けてしまう可能性があります。
そして、不要なデータがメモリに蓄積されると、メモリリークを引き起こす要因となり得るため注意が必要です。
💡 クロージャの参照を解除する
明示的にクロージャの参照を解除したい場合は
null
を代入することでガベージコレクションの対象にすることができます。
counter = null;
まずは、不要な参照を残さないように、スコープを適切に設計することが大切です。
その上で、明示的に参照を解除したい場合は、上記のようにnull
を代入することで、メモリリークを未然に防ぎましょう。
✅ まとめ
- メモリとは、データやプログラムを一時的に保管しておく場所
- JavaScriptでは、参照されなくなったデータはガベージコレクションによって自動的にメモリから削除される
- クロージャはスコープの参照を保持するため、参照を切らない限りメモリに残り続ける
- 不要になったクロージャには
null
を代入して参照を解除することで、GCの対象にできる