この記事では『クロージャ』について、以下の内容をサンプルコードを用いてわかりやすく解説します。
- クロージャとは
- クロージャの用途
- クロージャの特徴
クロージャとは
クロージャとは、関数とその関数が定義されたスコープを保持し続ける関数です。
具体的には、関数の中に内部関数を定義し、内部関数で外部関数の変数や関数にアクセスします。すると、外部関数が終了した後でも、内部関数が外部関数の変数や関数にアクセスし続けることが可能になります。この性質を用いると、特定の状態を保持し続けたり、動的に関数を生成することができます。
クロージャのサンプルコードを以下に示します。
function outerFunction() {
let outerVariable = 'I am from outer';
// outerFunction関数の内部でinnerFunction関数を定義している
function innerFunction() {
// innerFunction関数はouterFunction関数のスコープに定義された変数outerVariableにアクセスする
console.log(outerVariable);
}
return innerFunction; // outerFunctionの返り値はinnerFunction関数
}
// outerFunction()の返り値はinnerFunction関数なので、closure変数はinnerFunction関数を指している
const closure = outerFunction();
// innerFunction関数が実行される
closure(); // I am from outer と出力
上記のサンプルコードでは、outerFunction
という外部関数の中に、innerFunction
という内部関数が定義されています。また、outerFunction
のスコープ内で定義されたouterVariable
に、innerFunction
はアクセスしています。すると、outerFunction
が終了しても、innerFunction
はそのスコープ(outerVariable
)を保持し続けます。
このように、外部関数の変数にアクセスし続ける関数がクロージャです(上記のサンプルコードでは、innerFunction
がクロージャとなります)。
innerFunction
内で特定の変数(ここではouterVariable
)を参照し続けることで、関数が状態を保持しているかのように振舞っています。
補足
通常、innerFunction
は無名関数にすることが多いですが、今回は説明を分かりやすくするため、名前を付けた関数を使っています。
クロージャの用途
クロージャは以下のような場面でよく使われます。
- データをカプセル化する
- 動的な関数を作成する
各用途について順番に説明します。
データをカプセル化する
クロージャを使うことで、外部から直接アクセスできない変数を定義し、その変数にアクセスするためのインターフェースを提供できます。これにより、重要なデータや情報を外部から隠蔽することができます(データの隠蔽)。
サンプルコードを以下に示します。
function counter() {
//count変数は関数の外部からアクセスすることができない
let count = 0;
// これがクロージャ(無名関数)
return function () {
// count変数をインクリメントした値はクロージャによって保持される
count++;
return count;
};
}
const increment = counter();
console.log(increment()); // 1
console.log(increment()); // 2
上記のサンプルコードでは、counter
関数の中で宣言されているcount
変数は外部から直接変更できませんが、クロージャを通じてcounter
関数内のcount
変数にアクセスして、インクリメントすることができます。通常の関数であれば、関数の実行が完了すると宣言されている変数の値は解放されて、次回の実行時に初期化されてしまいますが、クロージャの中で外部関数の変数へアクセスしている場合には、クロージャによって変数の値を保持し続けることができます。
クロージャを使用しない場合、count
変数が外部から直接アクセスされるため、保護されません。サンプルコードを以下に示します。
let count = 0;
function increment() {
count++;
return count;
}
console.log(increment()); // 1
console.log(increment()); // 2
count = 100; // 外部から直接アクセス可能
console.log(increment()); // 101
上記のサンプルコードでは、count
変数はグローバルスコープに存在するため、外部から簡単にアクセスして変更することができます。クロージャを使用すれば、こうした直接アクセスを防ぐことができます。
動的な関数を作成する
クロージャを利用すると、特定の引数や状況に基づいて動的に異なる関数を生成することが可能です。
サンプルコードを以下に示します。
function createMultiplier(num) {
function multiplier(value) {
// createMultiplier関数の引数numはクロージャによって値が保持される
return value * num;
}
return multiplier;
}
const double = createMultiplier(2);
console.log(double(5)); // 10
const triple = createMultiplier(3);
console.log(triple(10)); // 30
上記のサンプルコードでは、createMultiplier
は動的に異なる関数(double
やtriple
)を生成し、クロージャによって生成された関数(double
やtriple
)内でnum
の値が保持されています。
クロージャを使用せずに同様の処理を行うと、動的に異なる関数を生成することができません。
function multiplier(value, num) {
return value * num;
}
console.log(multiplier(5, 2)); // 10
console.log(multiplier(10, 3)); // 30
上記のサンプルコードでは、関数は毎回引数としてnum
を受け取る必要があり、動的に異なる関数を生成することができません。
クロージャの特徴
クロージャには、以下のような特徴があります。
クロージャの特徴
- 外部関数のスコープへのアクセス
- 内部関数は外部関数のスコープ内にある変数や関数にアクセスできます。これにより、外部関数が終了した後でも、内部関数が外部関数の変数や関数にアクセスし続けることが可能になります。
- 状態の保持
- クロージャを使うことで、外部関数が実行された後でも、外部関数のスコープ内にある変数の状態を保持し続けることができます。
- メモリ効率の課題
- クロージャは外部スコープの変数を保持し続けるため、不要になったクロージャがメモリを消費し続けることがあります。これは、メモリリークの原因となることもあるため、クロージャを適切に解放する必要があります。
- 新しい関数を定義する
- クロージャは実行するたびに、新たな関数が定義されます。そのため、クロージャを異なる変数に代入した際は、それぞれ異なる状態(データ)を保持します。サンプルコードを以下に示します。
function counter() {
let count = 0;
function increment() {
// count変数をインクリメントした値はクロージャによって保持される
count = count + 1;
return count;
}
return increment;
}
// countUpとnewCountUpは異なるincrement関数を保持している
const countUp = counter();
const newCountUp = counter();
// countUpとnewCountUpは異なるincrement関数を指しているため、===演算子で比較するとfalseになる
console.log(countUp === newCountUp); // false
// それぞれのカウンタは独立しており、状態も別々に保持されているため、
// 1つのカウンタの影響が他方に及ぶことはない
console.log(countUp()); // => 1
console.log(newCountUp()); // => 1
本記事のまとめ
この記事では『クロージャ』について、以下の内容を説明しました。
- クロージャとは
- クロージャの用途
- クロージャの特徴
お読み頂きありがとうございました。