クロージャとは何か
クロージャとは、関数が「自分が定義された場所の外側の変数」へのアクセスを、実行後も持ち続ける仕組みです。ポイントは「どこで呼ぶか」ではなく「どこで定義したか」。関数は定義時点のレキシカルスコープ(外側の変数)を覚え、後からその関数だけを取り出しても、外側の変数を参照・更新できます。
なぜクロージャが成り立つのか(重要ポイントを深掘り)
JavaScriptは関数を作るとき、その関数が定義された外側スコープへの参照(スコープチェーン)を一緒に保存します。関数を返して外側の関数が終了しても、返された関数から外側変数が参照されている限り、メモリ上で保持され続けます。これにより「生きている関数(内側)」が「終わったはずの関数(外側)の変数」を使えるのです。
- 重要な直感: 「外側の関数の実行が終わった=変数が消える」ではありません。内側の関数がその変数を使い続けるなら、ガベージコレクタは消しません。
例題1:カウンター(最小のクロージャ例)
function createCounter() {
let count = 0; // 外側の変数
function increment() {
count++; // 外側の変数を参照・更新
return count; // 現在値を返す
}
return increment; // 関数を返す(クロージャ)
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
JavaScriptcreateCounter が終わっても count は消えません。increment が count を必要としている(参照している)ため、count は生き続けます。これがクロージャの基本動作です。
例題2:「プライベート変数」のように使う
function createUser(name) {
let points = 0; // 外から直接触れさせない
return {
getName: () => name,
addPoint: () => { points++; },
getPoints: () => points
};
}
const user = createUser("太郎");
user.addPoint();
user.addPoint();
console.log(user.getName()); // 太郎
console.log(user.getPoints()); // 2
JavaScript外側の変数 points や name は返したオブジェクトのメソッドからのみアクセスされます。外部から直接変更できないため、意図せぬ書き換えを防げます。クロージャにより「データの隠蔽」に近い構造を簡単に作れます。
例題3:関数を「つくる関数」(定義位置で中身が決まる)
function makeAdder(x) {
// x を覚えた加算関数を返す
return function(y) {
return x + y; // x は定義時の外側スコープにある
};
}
const add10 = makeAdder(10);
const add3 = makeAdder(3);
console.log(add10(5)); // 15
console.log(add3(7)); // 10
JavaScriptmakeAdder が返す関数は、それぞれ別の x を覚えています(別のスコープを持ち歩く)。「定義された場所」で何を覚えたかが、その後の振る舞いを決めます。
よくあるつまずき(深掘り)
ループでの変数取り違え(var と let の違い)
// 想定外の挙動(var はブロックスコープを作らない)
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log("var:", i), 0);
}
// var: 3, var: 3, var: 3
// 期待通り(let は各反復の i を閉じ込める)
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log("let:", j), 0);
}
// let: 0, let: 1, let: 2
JavaScriptクロージャは「どの変数を覚えるか」に敏感です。var は一つの関数スコープの同じ i を共有し、最後の値を出しがち。let は反復ごとに新しいスコープを作るため、意図した値が閉じ込められます。
値そのものではなく「変数への参照」を覚える
クロージャは「値のコピー」ではなく「変数の参照」を保持します。後から変数が更新されれば、閉じ込めた関数から見える値も更新後になります。この性質を理解しておくと、意図しない結果を避けやすくなります。
いつまで保持されるのか(メモリの感覚)
外側の変数は、それを参照する関数やオブジェクトが生きている限り保持されます。逆に、参照が一切なくなればガベージコレクタが解放します。長寿命のクロージャを大量に作ると、必要以上にメモリを使うことがあるため、「本当に必要なものだけを残す」意識が大切です。
まとめ
- クロージャは「関数が定義された場所の外側変数を覚えて、後からも使える」仕組み。
- 成り立つ理由は、関数がスコープチェーン(定義位置の外側スコープ)への参照を一緒に保存するから。
- 代表例はカウンター、プライベートな状態保持、関数生成(関数の工場)。
- ループでの
varとletの違い、参照を覚える性質、メモリの寿命に注意すると、予測可能で安全なコードになる。
まずは「小さな状態を関数の外側に置き、それを返した関数から操作する」という練習から始めると、クロージャの感覚が自然に身につきます。
