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