Articles

JavaScript Closure Tutorial – With JS Closure Example Code

Posted on

Zamknięcia – wielu z Was JavaScript devs prawdopodobnie słyszało ten termin wcześniej. Kiedy zaczynałem swoją podróż z JavaScriptem, często spotykałem się z zamknięciami. I myślę, że są one jednym z najważniejszych i najciekawszych pojęć w JavaScript.

Nie sądzisz, że są interesujące? To się często zdarza, gdy nie rozumiesz jakiejś koncepcji – nie uważasz jej za interesującą. (Nie wiem czy zdarza się to Tobie czy nie, ale tak jest w moim przypadku).

W tym artykule postaram się więc sprawić, aby domknięcia stały się dla Ciebie interesujące.

Zanim zagłębimy się w świat domknięć, najpierw zrozumiemy skoping leksykalny. Jeśli już o tym wiesz, pomiń następną część. W przeciwnym razie wskocz do niej, aby lepiej zrozumieć closures.

Skoping leksykalny

Możesz się zastanawiać – znam zakres lokalny i globalny, ale co to do cholery jest zakres leksykalny? Zareagowałem w ten sam sposób, kiedy usłyszałem ten termin. Nie ma się czym martwić! Przyjrzyjmy się temu bliżej.

To proste jak pozostałe dwa zakresy:

function greetCustomer() { var customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); // Hi! anchal } greetingMsg();}

Wyraźnie widać na powyższym przykładzie, że funkcja wewnętrzna ma dostęp do zmiennej funkcji zewnętrznej. To jest leksykalne skalowanie, gdzie zakres i wartość zmiennej jest określona przez to, gdzie jest ona zdefiniowana/utworzona (czyli jej pozycja w kodzie). Rozumiesz?

Wiem, że ten ostatni fragment mógł Cię zdezorientować. Więc pozwól mi zabrać Cię głębiej. Czy wiesz, że leksykalny scoping jest również znany jako statyczny scoping? Tak, to jest jego druga nazwa.

Istnieje również dynamiczne określanie zakresu, które jest obsługiwane przez niektóre języki programowania. Dlaczego wspomniałem o dynamicznym skalowaniu? Ponieważ może to pomóc lepiej zrozumieć zakres leksykalny.

Spójrzmy na kilka przykładów:

function greetingMsg() { console.log(customerName);// ReferenceError: customerName is not defined}function greetCustomer() { var customerName = "anchal"; greetingMsg();}greetCustomer();

Czy zgadzasz się z danymi wyjściowymi? Tak, to da błąd odniesienia. Dzieje się tak, ponieważ obie funkcje nie mają dostępu do zakresu siebie nawzajem, ponieważ są zdefiniowane oddzielnie.

Przyjrzyjrzyjmy się innemu przykładowi:

function addNumbers(number1) { console.log(number1 + number2);}function addNumbersGenerate() { var number2 = 10; addNumbers(number2);}addNumbersGenerate();

Powyższe dane wyjściowe będą 20 dla dynamicznie skalowanego języka. Języki, które obsługują leksykalne skalowanie, dadzą referenceError: number2 is not defined. Dlaczego?

Ponieważ w dynamicznym scopingu wyszukiwanie odbywa się najpierw w funkcji lokalnej, a następnie przechodzi do funkcji, która wywołała tę funkcję lokalną. Następnie wyszukuje w funkcji, która wywołała tę funkcję, i tak dalej, w górę stosu wywołań.

Jego nazwa wyjaśnia się sama – „dynamiczny” oznacza zmianę. Zakres i wartość zmiennej mogą być różne, ponieważ zależy to od miejsca, z którego wywoływana jest funkcja. Znaczenie zmiennej może się zmieniać w czasie wykonywania funkcji.

Zrozumiałeś ideę dynamicznego skalowania? Jeśli tak, to pamiętaj, że leksykalny scoping jest jego przeciwieństwem.

W leksykalnym scopingu, wyszukiwanie odbywa się najpierw w funkcji lokalnej, a następnie w funkcji, wewnątrz której ta funkcja jest zdefiniowana. Następnie wyszukuje w funkcji, wewnątrz której ta funkcja jest zdefiniowana i tak dalej.

Skopiowanie leksykalne lub statyczne oznacza, że zakres i wartość zmiennej jest określona od miejsca jej zdefiniowania. To się nie zmienia.

Przyjrzyjmy się jeszcze raz powyższemu przykładowi i spróbujmy samodzielnie rozgryźć wyjście. Tylko jedno przekręcenie – zadeklaruj number2 na górze:

var number2 = 2;function addNumbers(number1) { console.log(number1 + number2);}function addNumbersGenerate() { var number2 = 10; addNumbers(number2);}addNumbersGenerate();

Czy wiesz już jakie będzie wyjście?

Poprawnie – jest to 12 dla języków z zakresem leksykalnym. Dzieje się tak dlatego, że najpierw zagląda do funkcji addNumbers (innermost scope), a następnie wyszukuje do wewnątrz, gdzie ta funkcja jest zdefiniowana. Ponieważ dostaje number2 zmienną, co oznacza, że wyjściem jest 12.

Możesz się zastanawiać, dlaczego spędziłem tyle czasu na leksykalnym skalowaniu tutaj. To jest artykuł o zamykaniu, a nie o skalowaniu leksykalnym. Ale jeśli nie wiesz o leksykalnym określaniu zakresu, to nie zrozumiesz domknięć.

Dlaczego? Odpowiedź uzyskasz, gdy przyjrzymy się definicji domknięcia. Więc wejdźmy na tor i wróćmy do domknięć.

Czym jest domknięcie?

Spójrzmy na definicję domknięcia:

Domknięcie jest tworzone, gdy funkcja wewnętrzna ma dostęp do swoich zmiennych i argumentów funkcji zewnętrznej. Funkcja wewnętrzna ma dostęp do –
1. Własnych zmiennych.
2. Zmiennych i argumentów funkcji zewnętrznej.
3. Zmiennych globalnych.

Czekaj, czy to jest definicja zamknięcia, czy leksykalnego skalowania? Obie definicje wyglądają tak samo. Czym się różnią?

Cóż, właśnie dlatego zdefiniowałem zakres leksykalny powyżej. Ponieważ zamknięcia są związane z leksykalno-statystycznym zakresem.

Ponownie spójrzmy na jego inną definicję, która powie ci, jak zamknięcia są różne.

Zamknięcie jest wtedy, gdy funkcja jest w stanie uzyskać dostęp do swojego zakresu leksykalnego, nawet jeśli ta funkcja wykonuje się poza swoim zakresem leksykalnym.

Or,

Funkcje wewnętrzne mogą uzyskać dostęp do swojego zakresu nadrzędnego, nawet po tym, jak funkcja nadrzędna jest już wykonana.

Konflikt? Nie martw się, jeśli jeszcze nie załapałeś o co chodzi. Mam przykłady, które pomogą Ci lepiej zrozumieć. Zmodyfikujmy pierwszy przykład leksykalnego skalowania:

function greetCustomer() { const customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); } return greetingMsg;}const callGreetCustomer = greetCustomer();callGreetCustomer(); // output – Hi! anchal

Różnica w tym kodzie polega na tym, że zwracamy wewnętrzną funkcję i wykonujemy ją później. W niektórych językach programowania, zmienna lokalna istnieje podczas wykonywania funkcji. Ale gdy funkcja zostanie wykonana, te zmienne lokalne nie istnieją i nie będą dostępne.

Tutaj jednak sytuacja jest inna. Po wykonaniu funkcji nadrzędnej, funkcja wewnętrzna (funkcja zwracana) nadal może uzyskać dostęp do zmiennych funkcji nadrzędnej. Tak, dobrze zgadłeś. Powodem są zamknięcia.

Wewnętrzna funkcja zachowuje swój zakres leksykalny, gdy funkcja nadrzędna jest wykonywana, a zatem później ta wewnętrzna funkcja może uzyskać dostęp do tych zmiennych.

Aby lepiej to zrozumieć, użyjmy metody dir() w konsoli, aby przejrzeć listę właściwości callGreetCustomer:

console.dir(callGreetCustomer);

Z powyższego obrazka, widać, jak funkcja wewnętrzna zachowuje swój zakres rodzica (customerName), gdy greetCustomer() jest wykonywany. A później użył customerName, gdy callGreetCustomer() został wykonany.

Mam nadzieję, że ten przykład pomógł Ci lepiej zrozumieć powyższą definicję domknięcia. I może teraz uważasz domknięcia za nieco bardziej zabawne.

Co dalej? Uczyńmy ten temat bardziej interesującym, przyglądając się różnym przykładom.

Przykłady domknięć w akcji

function counter() { let count = 0; return function() { return count++; };}const countValue = counter();countValue(); // 0countValue(); // 1countValue(); // 2

Za każdym razem, gdy wywołujemy countValue, wartość zmiennej count jest zwiększana o 1. Czekaj – myślałeś, że wartość count wynosi 0?

Cóż, to byłby błąd, ponieważ domknięcie nie działa z wartością. Przechowuje ono referencję do zmiennej. Dlatego też, gdy zaktualizujemy wartość, odbije się to w drugim lub trzecim wywołaniu i tak dalej, ponieważ domknięcie przechowuje referencję.

Czy czujesz się teraz trochę jaśniej? Spójrzmy na inny przykład:

function counter() { let count = 0; return function () { return count++; };}const countValue1 = counter();const countValue2 = counter();countValue1(); // 0countValue1(); // 1countValue2(); // 0countValue2(); // 1

Mam nadzieję, że zgadłeś właściwą odpowiedź. Jeśli nie, oto powód. Ponieważ countValue1 i countValue2, oba zachowują swój własny zakres leksykalny. Mają one niezależne środowiska leksykalne. Możesz użyć dir(), aby sprawdzić wartość ] w obu przypadkach.

Przyjrzyjrzyjmy się trzeciemu przykładowi.

Ten jest nieco inny. W nim musimy napisać funkcję, aby osiągnąć wynik:

const addNumberCall = addNumber(7);addNumberCall(8) // 15addNumberCall(6) // 13

Proste. Wykorzystaj nowo zdobytą wiedzę na temat domknięć:

function addNumber(number1) { return function (number2) { return number1 + number2; };}

Teraz spójrzmy na kilka podchwytliwych przykładów:

function countTheNumber() { var arrToStore = ; for (var x = 0; x < 9; x++) { arrToStore = function () { return x; }; } return arrToStore;}const callInnerFunctions = countTheNumber();callInnerFunctions() // 9callInnerFunctions() // 9

Każdy element tablicy, który przechowuje funkcję, da ci wyjście równe 9. Czy dobrze zgadłeś? Mam nadzieję, że tak, ale nadal pozwól mi powiedzieć ci powód. Dzieje się tak z powodu zachowania zamknięcia.

Zamknięcie przechowuje referencję, a nie wartość. Przy pierwszym uruchomieniu pętli wartość x wynosi 0, przy drugim x wynosi 1, i tak dalej. Ponieważ domknięcie przechowuje referencję, za każdym razem, gdy pętla jest uruchamiana, zmienia wartość x. I w końcu wartość x będzie wynosić 9. Tak więc callInnerFunctions() daje wartość wyjściową 9.

A co jeśli chcesz mieć wartość wyjściową od 0 do 8? Proste! Użyj zamknięcia.

Pomyśl o tym, zanim spojrzysz na poniższe rozwiązanie:

function callTheNumber() { function getAllNumbers(number) { return function() { return number; }; } var arrToStore = ; for (var x = 0; x < 9; x++) { arrToStore = getAllNumbers(x); } return arrToStore;}const callInnerFunctions = callTheNumber();console.log(callInnerFunctions()); // 0console.log(callInnerFunctions()); // 1

Tutaj utworzyliśmy osobny zakres dla każdej iteracji. Możesz użyć console.dir(arrToStore), aby sprawdzić wartość x w ] dla różnych elementów tablicy.

To wszystko! Mam nadzieję, że możesz teraz powiedzieć, że uważasz domknięcia za interesujące.

Aby przeczytać inne moje artykuły, sprawdź mój profil tutaj.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *