Domknięcie w TypeScript
W tym artykule wyjaśnimy domknięcia w TypeScript.
YouTube Video
Domknięcie w TypeScript
Czym jest domknięcie?
Domknięcie oznacza możliwość zachowania odwołań do zakresu (środowiska), w którym funkcja została zdefiniowana, nawet jeśli funkcja jest wywoływana poza tym zakresem. Poniżej domknięcia zostaną wyjaśnione wraz z adnotacjami typów.
Mówiąc prościej, domknięcie łączy funkcję oraz środowisko zmiennych, w którym funkcja została zdefiniowana, co umożliwia dostęp do tego środowiska podczas wywołania funkcji.
Podstawowy mechanizm domknięć
W TypeScript, gdy funkcja jest zdefiniowana wewnątrz innej funkcji, staje się oczywiste, że funkcja wewnętrzna ma dostęp do zmiennych funkcji zewnętrznej. Oto podstawowy przykład domknięcia z adnotacjami typów.
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"
- Typ zwracany
outerFunction
to() => void
, co oznacza, że zwraca ona funkcję. - Typ
innerFunction
jest jawnie ustawiony navoid
, co oznacza, że nie ma ona wartości zwracanej.
Zastosowania i zalety domknięć
Enkapsulacja danych
Poniżej znajduje się przykład domknięcia z adnotacjami typów służący do enkapsulacji danych.
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
- Funkcja
createCounter
zwraca funkcję typu() => number
. - Zmienna
count
jest zdefiniowana jako typnumber
i jest modyfikowana wewnątrz domknięcia.
Funkcje wyższego rzędu
Domknięcia są przydatne podczas tworzenia funkcji wyższego rzędu. Poniżej znajduje się przykład funkcji wyższego rzędu z wyraźnymi adnotacjami typów.
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
przyjmuje argument typunumber
i zwraca funkcję typu(value: number) => number
.- Funkcja wewnętrzna również przyjmuje
value
jako typnumber
i zwraca wynik także jako typnumber
.
Przykład implementacji domknięcia w TypeScript
Zaimplementuj licznik z ograniczonym zakresem jako domknięcie z dodanymi adnotacjami typów.
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"
- Funkcja
rangeCounter
zwraca funkcję, która zwracanumber
lubstring
. - W funkcji wewnętrznej, jeśli
count
przekroczy wartośćmax
, zwraca komunikat typustring
; w przeciwnym razie zwraca wartość typunumber
.
Środki ostrożności przy używaniu domknięć
Potencjalne wycieki pamięci spowodowane przez domknięcia
Domknięcia mogą zachowywać zmienne spoza swojego zakresu, co czasami może powodować wycieki pamięci. Niepotrzebne domknięcia muszą być jawnie zwalniane z pamięci.
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
- W tym kodzie
largeArray
utworzona wcreateLeak
powinna zostać zwolniona po wyjściu z zakresu, ale tak się nie dzieje, ponieważ domknięcie przechwytujelargeArray
. Dopóki istniejeleakyFunction
, ta niepotrzebna pamięć będzie nadal zajmowana. - Gdy obiekt lub zmienna nie są już potrzebne, ustawienie ich referencji na
null
pozwala mechanizmowi garbage collectora wykryć je i zwolnić pamięć.
Niewłaściwe zastosowanie domknięć w pętlach
Tworząc domknięcia wewnątrz pętli, mogą wystąpić problemy z odwoływaniem się do tej samej zmiennej. Poniższy przykład ilustruje przypadek, w którym zmienna i
nie działa poprawnie.
1for (var i: number = 0; i < 3; i++) {
2 setTimeout((): void => {
3 console.log(i);
4 }, 1000);
5}
6// Output: 3, 3, 3
Kod nie daje oczekiwanego wyniku, ponieważ i
odnosi się do wartości 3 na końcu pętli. Aby to naprawić, użyj let
, aby rozdzielić zakres, lub zastosuj funkcję natychmiastowo wywoływaną.
1for (let i: number = 0; i < 3; i++) {
2 setTimeout((): void => {
3 console.log(i);
4 }, 1000);
5}
6// Output: 0, 1, 2
Dzięki użyciu let
, zakres zmiennej i
jest rozdzielony dla każdej iteracji pętli, co daje oczekiwane rezultaty.
Podsumowanie
W TypeScript domknięcia mogą prowadzić do bezpieczniejszego i bardziej przewidywalnego kodu, dzięki wykorzystaniu systemu typów. Właściwe wykorzystanie domknięć umożliwia enkapsulację danych i elastyczne projektowanie funkcji wyższego rzędu. Ponadto należy zachować ostrożność przy zarządzaniu pamięcią oraz niezamierzonych odwołaniach do zakresu podczas korzystania z domknięć.
Możesz śledzić ten artykuł, korzystając z Visual Studio Code na naszym kanale YouTube. Proszę również sprawdzić nasz kanał YouTube.