TypeScriptにおけるクロージャー

TypeScriptにおけるクロージャー

この記事ではTypeScriptにおけるクロージャーについて説明します。

YouTube Video

TypeScriptにおけるクロージャー

クロージャーとは?

**クロージャー(Closure)**は、関数が定義されたスコープ外で呼び出されても、その関数が定義されたときのスコープ(環境)への参照を保持する機能を指します。以下では、型指定を含めてクロージャーを説明します。

簡単に言えば、クロージャーは「関数」と「その関数が定義されたときの変数環境」をまとめたもので、関数が呼び出される際に、その変数環境にアクセスできる状態が保持されます。

クロージャーの基本的な仕組み

TypeScriptでは、関数が他の関数内で定義される場合、その内部関数が外部関数の変数にアクセスできることがわかります。型を指定したクロージャーの基本的な例を以下に示します。

 1function outerFunction(): () => void {
 2    let outerVariable: string = "I am from outer function";
 3
 4    function innerFunction(): void {
 5        // The inner function accesses the variable of the outer function
 6        console.log(outerVariable);
 7    }
 8
 9    return innerFunction;
10}
11
12const closure: () => void = outerFunction();
13closure();  // "I am from outer function"
  • outerFunctionは、内部関数のinnerFunctionを返します。
  • innerFunctionは、外部関数の変数outerVariableの値を表示します。

クロージャーの用途と利点

データのカプセル化

データをカプセル化するためのクロージャーの型指定された例を以下に示します。

 1function createCounter(): () => number {
 2    let count: number = 0;
 3
 4    return function (): number {
 5        count += 1;
 6        return count;
 7    };
 8}
 9
10const counter: () => number = createCounter();
11console.log(counter());  // 1
12console.log(counter());  // 2
13console.log(counter());  // 3
  • createCounter関数は、number型を返す関数を返します。
  • 変数countnumber型で定義し、クロージャー内で操作されます。

高階関数(Higher-Order Functions)

クロージャーは高階関数を作成する際に有用です。ここで、高階関数とは、関数を引数として受け取ったり、関数を戻り値として返す関数のことです。以下は、型を明確に指定した高階関数の例です。

 1function createMultiplier(multiplier: number): (value: number) => number {
 2    return function (value: number): number {
 3        return value * multiplier;
 4    };
 5}
 6
 7const double: (value: number) => number = createMultiplier(2);
 8console.log(double(5));  // 10
 9
10const triple: (value: number) => number = createMultiplier(3);
11console.log(triple(5));  // 15
  • createMultiplierは、引数に受け取った数値で掛け算をする関数を作る高階関数です。
  • 内部の掛け算をする関数も数値を受け取り、掛け算の結果を数値として返します。

TypeScriptでのクロージャーの実装例

範囲付きカウンターをクロージャーとして実装する例も見てみましょう。

 1function rangeCounter(min: number, max: number): () => number | string {
 2    let count: number = min;
 3
 4    return function (): number | string {
 5        if (count <= max) {
 6            return count++;
 7        } else {
 8            return `Count has exceeded the maximum value: ${max}`;
 9        }
10    };
11}
12
13const counter: () => number | string = rangeCounter(1, 5);
14
15console.log(counter());  // 1
16console.log(counter());  // 2
17console.log(counter());  // 3
18console.log(counter());  // 4
19console.log(counter());  // 5
20console.log(counter());  // "Count has exceeded the maximum value: 5"
  • rangeCounter関数は、numberまたはstringを返す関数を戻り値として持ちます。
  • 内部関数では、countmaxを超えた場合にstring型のメッセージを返し、それ以外はnumber型を返します。

クロージャー利用の際の注意点

クロージャーによるメモリリークの可能性

クロージャーは外部スコープの変数を保持するため、場合によってはメモリリークが発生する可能性があります。不要になったクロージャーは明示的にメモリから解放する必要があります。

 1function createLeak(): () => void {
 2    // Large array consuming significant memory
 3    const largeArray: string[] = new Array(1000000).fill("leak");
 4
 5    // Closure capturing `largeArray`
 6    return function (): void {
 7        console.log(largeArray[0]); // Using the captured array
 8    };
 9}
10
11// Create a closure that holds a reference to the large array
12let leakyFunction = createLeak();
13
14// The large array is not released as `leakyFunction` still references it
15
16// When the object is no longer needed
17leakyFunction = null; // Explicitly remove the reference
  • このコードでは、createLeak 内で作成された largeArray は本来スコープを離れると解放されるべきですが、クロージャーが largeArray をキャプチャしているため、解放されません。leakyFunction が存在する限り、この不要なメモリが保持され続けます。
  • オブジェクトや変数が不要になった場合、その参照を null に設定することで、ガベージコレクターがそれを検知してメモリを解放できます。

ループ内でのクロージャーの誤用

ループ内でクロージャーを作成する際、同じ変数を参照する問題が発生することがあります。以下の例では、varで宣言された変数iが正しく動作しない例を示します。

1for (var i: number = 0; i < 3; i++) {
2    setTimeout((): void => {
3        console.log(i);
4    }, 1000);
5}
6// Output: 3, 3, 3

このコードは、iがループ終了時の値である3を参照するため、意図した結果になりません。これを修正するには、letを使ってスコープを分離するか、即時関数を使用します。

1for (let i: number = 0; i < 3; i++) {
2    setTimeout((): void => {
3        console.log(i);
4    }, 1000);
5}
6// Output: 0, 1, 2

letを使用することで、iのスコープが各ループごとに分離され、期待通りの結果が得られます。

まとめ

TypeScriptにおけるクロージャーは、型システムを活用することで、より安全かつ予測可能なコードを実現できます。クロージャーを正しく利用することで、データのカプセル化や高階関数の柔軟な設計が可能となります。また、クロージャーの使用時には、メモリ管理や意図しないスコープ参照に注意が必要です。

YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。

YouTube Video