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"
outerFunction
zwraca funkcję wewnętrznąinnerFunction
.innerFunction
wyświetla wartość zmiennejouterVariable
należącej do funkcji zewnętrznej.
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
createCounter
zwraca funkcję, która zwraca wartość typunumber
.- 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. Tutaj funkcja wyższego rzędu to taka funkcja, która przyjmuje inną funkcję jako argument lub zwraca funkcję jako rezultat. 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
to funkcja wyższego rzędu, która tworzy funkcję mnożącą przez liczbę otrzymaną jako argument.- Wewnętrzna funkcja mnożenia również przyjmuje liczbę i zwraca wynik mnożenia jako liczbę.
Przykład implementacji domknięcia w TypeScript
Przyjrzyjmy się również przykładowi implementacji licznika z zakresem jako domknięcia.
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 pokazuje sytuację, w której zmienna i
zadeklarowana za pomocą var
nie zachowuje się 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.