概要
クロージャ(Closure) とは、
「関数が定義されたときのスコープ(外部変数)を保持し続ける仕組み」です。
つまり、
- 関数が“どこで定義されたか”を基準にスコープが決まる
- 定義時の外部変数を記憶し、関数の実行時にも参照できる
- React Hooks(useEffect, useCallback など)や非同期処理(setTimeout など)にも必須の概念
です。
目次
基本構文
function outer() {
let message = "Hello Closure";
function inner() {
console.log(message); // 外側スコープの変数を参照
}
return inner;
}
const fn = outer();
fn(); // "Hello Closure"
解説:
-
inner関数は、outer関数のスコープ内で定義されたため、outerが実行済み(通常なら消える変数)でも、messageを保持できる。 - この「スコープを保持し続ける関数」が クロージャ。
setTimeoutとクロージャ
function logLater() {
let message = "Hello";
setTimeout(function () {
console.log(message); // ← クロージャで message を保持
}, 1000);
}
logLater();
ポイント:
-
setTimeoutの中の無名関数は、非同期的に呼び出される。 - しかし、外側の
messageは破棄されず保持されている。 - これが「クロージャによるスコープの保持」。
React Hooksとクロージャ
React Hooks(特に useEffect、useCallback、useMemo)も
クロージャの性質を前提 に動作しています。
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
console.log(count); // ← クロージャで count を保持
}, 1000);
return () => clearTimeout(timer);
}, []);
}
解説:
-
setTimeoutの中の関数は、countの「初期値」を保持したまま動作。 -
useEffectの依存配列が[]の場合、再レンダー時も新しいcountを参照しない。 - これがいわゆる Stale Closure(古いクロージャ)問題 の原因。
活用例
1. プライベート変数の実現
クロージャを使うことで、外部から直接アクセスできない「プライベート変数」を作れます。
function createUser(name) {
let secret = "classified";
return {
getName: () => name,
getSecret: () => secret,
setSecret: (s) => { secret = s; }
};
}
const user = createUser("Alice");
console.log(user.getName()); // Alice
console.log(user.getSecret()); // classified
user.setSecret("updated");
console.log(user.getSecret()); // updated
用途:
ユーザー情報・トークン・設定値などを外部から隠したいとき。
クラス構文を使わずに「カプセル化」を実現。
2. 関数ファクトリ
クロージャを使うことで、状態を持つ関数を量産できます。
function createValidator(type) {
return function (value) {
if (type === "email") return value.includes("@");
if (type === "number") return !isNaN(value);
return false;
};
}
const emailValidator = createValidator("email");
const numberValidator = createValidator("number");
console.log(emailValidator("test@mail.com")); // true
console.log(numberValidator("abc")); // false
用途:
バリデーションやフィルタ関数の生成。
メリット: 各関数が独立した状態を持てる。
3. 非同期処理での状態保持
非同期処理の中で変数を保持するのもクロージャの代表的な用途です。
function delayLog(message) {
setTimeout(() => {
console.log(message); // クロージャで message を保持
}, 1000);
}
delayLog("Hello Async!");
用途:
ログ・UI更新・トラッキング処理など、非同期イベント時に値を保持。
4. Reactでの利用とStale Closure
useEffect(() => {
const timer = setTimeout(() => {
console.log(count); // count はクロージャで保持される
}, 1000);
return () => clearTimeout(timer);
}, []);
用途:
ステートのスナップショット保持やイベントの最適化。
注意:
count の最新値を取得したい場合は、依存配列に count を追加する必要があります。
古い値を保持したままになると、Stale Closure と呼ばれるバグの原因になります。
まとめ
| シーン | クロージャの役割 | メリット |
|---|---|---|
| プライベート変数 | 外部アクセスの遮断 | データの安全性向上 |
| 関数ファクトリ | 状態を持つ関数生成 | コード再利用性 |
| 非同期処理 | 遅延実行時の状態保持 | 非同期安全性 |
| React Hooks | ステートとイベント整合性 | 副作用の正確な制御 |
クロージャは「隠す・保持する・分離する」ための設計パターン。