クロージャって何者?
クロージャは、
「外側のスコープの変数を“覚えたまま”生きている関数」 です。
もう少しだけ丁寧に言うと、
ある関数の中で定義された関数が、
その外側の変数にアクセスし続けられる仕組み
これがクロージャです。
難しそうに聞こえるけれど、
実は「レキシカルスコープの自然な結果」にすぎません。
ちゃんと順番に見ていけば、ふわっとした怖さは消えます。
一番シンプルなクロージャの例(まずは感覚から)
外側の変数を“覚えている”関数
まずは、動きを見てください。
function createMessage() {
const text = "こんにちは";
function showMessage() {
console.log(text);
}
return showMessage;
}
const fn = createMessage();
fn(); // こんにちは
JavaScriptここで起きていることを言葉で追うと、
createMessageの中でtextを宣言- 同じく中で
showMessageを定義(textを使っている) createMessage()を呼ぶと、showMessageが返ってくるfn()としてshowMessageをあとから呼ぶ- そのときも
textにアクセスできている
ポイントはここです。
createMessage の実行は終わっているのに、
その中の変数 text がまだ生きていて、showMessage から見えている。
この「外側の変数を覚えたままの関数」が、クロージャです。
なぜそんなことが起きるのか(レキシカルスコープとの関係)
「どこで定義されたか」を関数が覚えている
さっきのコードを、スコープの観点で見るとこうなります。
function createMessage() {
const text = "こんにちは"; // createMessage のローカルスコープ
function showMessage() {
console.log(text); // 外側の text を参照
}
return showMessage;
}
JavaScriptshowMessage は、
「createMessage の中で定義された関数」です。
JavaScript はレキシカルスコープなので、
関数は「自分が定義されたときの外側スコープ」を覚えます。
だから createMessage() の実行が終わっても、showMessage は「自分が生まれた場所のスコープ(=text がある場所)」を
一緒に持ち歩いているイメージになります。
ここが重要です。
クロージャは「特別なモンスター」ではなく、
“レキシカルスコープがある世界で、外側の変数を覚えた関数”に名前がついただけ。
クロージャの定番例:カウンター
「外から直接触れないけど、増やせる数」
クロージャの一番有名な例が「カウンター」です。
function createCounter() {
let count = 0; // 外からは見えない“秘密の変数”
function increment() {
count += 1;
console.log(count);
}
return increment;
}
const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3
JavaScriptここでのポイントを整理します。
count は createCounter のローカル変数
外から count に直接アクセスすることはできないincrement だけが count を読み書きできるcreateCounter() の実行が終わっても、count は消えない
つまり、
「count は外からは見えない“プライベートな状態”で、counter() を呼ぶことでだけ変えられる」
という状態になっています。
これがクロージャの強さです。
「状態を外から守りつつ、必要な操作だけを公開する」
という設計が、関数だけで実現できます。
「外から見えないけど、関数からは見える」という感覚
直接触れない=安全、関数経由=コントロールされた入口
さっきのカウンターをもう少し触ってみます。
const counter = createCounter();
// これは OK
counter(); // 1
counter(); // 2
// これは NG(できない)
console.log(count); // ReferenceError: count is not defined
JavaScriptcount は createCounter の中のローカル変数なので、
外からは存在しないことになっています。
でも counter() を呼ぶと、increment の中から count にアクセスできる。
ここが重要です。
クロージャは「変数を完全に隠す」のではなく、
“アクセスの仕方を関数経由に限定する”ことで、
状態を安全に扱えるようにしている。
実務では、
「外から勝手に書き換えられたくないけど、
特定の操作だけは許可したい状態」
を扱うときに、クロージャがよく使われます。
もう一つの例:メッセージを覚えた関数を作る
「設定だけ先にしておいて、あとで使う」
次のような関数を考えます。
function createGreeter(name) {
function greet() {
console.log("こんにちは、" + name + "さん");
}
return greet;
}
const greetTaro = createGreeter("太郎");
const greetHanako = createGreeter("花子");
greetTaro(); // こんにちは、太郎さん
greetHanako(); // こんにちは、花子さん
JavaScriptここで起きていることは、
createGreeter("太郎") を呼ぶと、name = "太郎" を覚えた greet が返ってくる
createGreeter("花子") を呼ぶと、name = "花子" を覚えた別の greet が返ってくる
ということです。
つまり、
「それぞれ違う“外側の状態”を覚えた関数を量産している」
これもクロージャです。
「設定を先にしておいて、あとから使う関数」を作るときに、
クロージャはとても自然に登場します。
クロージャで“混乱しやすいポイント”を先に潰しておく
「いつの値を見ているのか」が分からなくなる問題
よくある混乱は、
「外側の変数があとから変わったら、クロージャはどっちを見るの?」
というものです。
次のコードを見てください。
function create() {
let value = 1;
function show() {
console.log(value);
}
value = 2;
return show;
}
const fn = create();
fn(); // 2
JavaScriptshow は value の「値」ではなく、
「変数そのもの」を覚えています。
だから、value = 2; と書き換えたあとに fn() を呼ぶと、
その時点の value(2)が表示されます。
ここが重要です。
クロージャが覚えているのは「値のコピー」ではなく「変数への参照」。
だから、外側の変数が変われば、クロージャから見える値も変わる。
この感覚がつかめると、
「なんでこの値になるんだ?」というモヤモヤが減ります。
初心者として「クロージャの基礎」で本当に押さえてほしいこと
ここまでの話を、あえて最低限に絞るとこうです。
クロージャとは、
「外側のスコープの変数を覚えたまま生きている関数」
であり、
それは、
「関数が“定義された場所のスコープ”を覚えている(レキシカルスコープ)」
というルールの自然な結果。
そのおかげで、
外から直接触れられない“プライベートな状態”を持てる
設定だけ先にしておいて、あとから使う関数を作れる
ということができる。
まずはこの2つのパターンを、自分の手で書いてみてください。
// 1. カウンター
function createCounter() {
let count = 0;
return function () {
count++;
console.log(count);
};
}
// 2. 名前を覚えた挨拶関数
function createGreeter(name) {
return function () {
console.log("こんにちは、" + name + "さん");
};
}
JavaScript「外側の変数を覚えた関数を返す」
この形が体に馴染んだ瞬間、
クロージャは“難しい概念”ではなく、
「めちゃくちゃ便利な日常ツール」に変わります。
