Замыкания в 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 возвращает функцию, которая возвращает либо 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, объявленная с помощью var, ведет себя некорректно.

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