TypeScript 中的閉包

TypeScript 中的閉包

在本文中,我們將解釋 TypeScript 中的閉包。

YouTube Video

TypeScript 中的閉包

什麼是閉包?

閉包 是指儘管函數在其作用域之外被調用,仍然能夠保留對該函數定義時的作用域(環境)的引用的能力。下面將包括類型註解來解釋閉包。

簡單地說,閉包將函數和函數定義時的變量環境結合在一起,允許在調用函數時訪問該環境。

閉包的基本機制

在 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)的函式。
  • count 變量被定義為 number 類型,並在閉包內被操作。

高階函數

閉包在創建高階函數時非常有用。這裡,高階函數是指將另一個函數作為參數傳遞或是回傳一個函數作為結果的函數。以下是一個帶有明確類型註解的高階函數示例。

 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 函數返回的函數返回值可以是 numberstring
  • 在內部函式中,如果 count 超過 max,它會返回一個 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