Замыкания в 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-канал.