Замыкания в 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() => void, что указывает на то, что она возвращает функцию.
  • Тип функции innerFunction явно установлен как void, что указывает на отсутствие возвращаемого значения.

Использование и преимущества замыканий

Инкапсуляция данных

Ниже приведен пример замыкания с аннотациями типов для инкапсуляции данных.

 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 принимает аргумент типа number и возвращает функцию типа (value: number) => number.
  • Внутренняя функция также принимает value как тип number и возвращает результат в виде типа number.

Пример реализации замыканий в 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.
  • Во внутренней функции, если 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
  • В этом коде largeArray, созданный внутри createLeak, должен быть освобожден, когда выходит из области видимости, но этого не происходит, так как замыкание захватывает largeArray. Пока существует leakyFunction, эта ненужная память будет продолжать удерживаться.
  • Когда объект или переменная больше не нужны, их ссылка, установленная в null, позволяет сборщику мусора обнаружить их и освободить память.

Неправильное использование замыканий в циклах

При создании замыканий внутри цикла могут возникнуть проблемы с обращением к одной и той же переменной. Следующий пример демонстрирует случай, когда переменная 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 замыкания могут позволить создавать более безопасный и предсказуемый код за счет использования системы типов. Правильное использование замыканий позволяет инкапсулировать данные и создавать гибкий дизайн функций высшего порядка. Кроме того, при использовании замыканий необходимо быть внимательным к управлению памятью и непреднамеренным ссылкам на области видимости.

Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.

YouTube Video