Mistrzowska rozmowa kwalifikacyjna w JavaScript: What is a Closure?

„Master the JavaScript Interview” jest serią postów mających na celu przygotowanie kandydatów do odpowiedzi na typowe pytania, z którymi prawdopodobnie spotkają się aplikując na stanowisko JavaScript średniego i wyższego szczebla. Są to pytania, których często używam w prawdziwych rozmowach kwalifikacyjnych.

Rozpoczynam serię pytaniem o 40 tys. dolarów. Jeśli odpowiesz na to pytanie źle, jest duża szansa, że nie zostaniesz zatrudniony. Jeśli się zatrudnisz, jest duża szansa, że zostaniesz zatrudniony jako młodszy programista, niezależnie od tego, jak długo pracujesz jako programista. Młodsi programiści zarabiają średnio 40 tys. dolarów rocznie mniej niż bardziej doświadczeni inżynierowie oprogramowania.

Zamknięcia są ważne, ponieważ kontrolują, co jest, a co nie jest w zakresie danej funkcji, wraz z tym, które zmienne są współdzielone między rodzeństwem funkcji w tym samym zakresie. Zrozumienie, jak zmienne i funkcje odnoszą się do siebie jest krytyczne dla zrozumienia, co dzieje się w twoim kodzie, zarówno w funkcjonalnych, jak i obiektowych stylach programowania.

Powód, dla którego brak tego pytania jest tak niekorzystny podczas rozmowy kwalifikacyjnej jest taki, że nieporozumienia dotyczące tego, jak działają domknięcia są dość wyraźną czerwoną flagą, która może ujawnić brak głębokiego doświadczenia, nie tylko w JavaScript, ale w każdym języku, który opiera się na domknięciach (Haskell, C#, Python, itp…).

Kodowanie w JavaScript bez zrozumienia domknięć jest jak próba mówienia po angielsku bez zrozumienia zasad gramatyki – możesz być w stanie przekazać swoje pomysły, ale prawdopodobnie trochę niezręcznie.

Będziesz również narażony na nieporozumienia, gdy próbujesz zrozumieć, co ktoś inny napisał.

Nie tylko powinieneś wiedzieć, czym jest zamknięcie, ale powinieneś wiedzieć, dlaczego ma ono znaczenie i być w stanie łatwo odpowiedzieć na kilka możliwych przypadków użycia zamknięć.

Zamknięcia są często używane w JavaScript do ochrony danych obiektów, w obsłudze zdarzeń i funkcjach wywołania zwrotnego, a także w aplikacjach częściowych, currying i innych funkcjonalnych wzorcach programowania.

Jeśli nie potrafisz odpowiedzieć na to pytanie, może Cię to kosztować pracę lub ~$40k/rok.

Bądź przygotowany na szybką kontynuację: „Czy możesz wymienić dwa powszechne zastosowania zamknięć?”

Co to jest zamknięcie?

Zamknięcie jest kombinacją funkcji połączonej razem (zamkniętej) z odniesieniami do jej otaczającego stanu (środowiska leksykalnego). Innymi słowy, domknięcie daje dostęp do zakresu zewnętrznej funkcji z wewnętrznej funkcji. W JavaScript, domknięcia są tworzone za każdym razem, gdy tworzona jest funkcja, w czasie tworzenia funkcji.

Aby użyć domknięcia, zdefiniuj funkcję wewnątrz innej funkcji i wyeksponuj ją. Aby wyeksponować funkcję, zwróć ją lub przekaż do innej funkcji.

Wewnętrzna funkcja będzie miała dostęp do zmiennych w zakresie zewnętrznej funkcji, nawet po jej powrocie.

Używanie domknięć (przykłady)

Pomiędzy innymi, domknięcia są powszechnie używane do zapewnienia obiektom prywatności danych. Prywatność danych jest istotną właściwością, która pomaga nam programować do interfejsu, a nie do implementacji. Jest to ważna koncepcja, która pomaga nam budować bardziej solidne oprogramowanie, ponieważ szczegóły implementacji są bardziej prawdopodobne, że zmienią się w sposób niszczący niż kontrakty interfejsu.

„Programuj do interfejsu, nie do implementacji.”
Design Patterns: Elements of Reusable Object Oriented Software

W JavaScript, domknięcia są podstawowym mechanizmem używanym do zapewnienia prywatności danych. Kiedy używasz zamknięć dla prywatności danych, zamknięte zmienne są tylko w zakresie wewnątrz zawierającej (zewnętrznej) funkcji. Nie można dostać się do danych z zewnętrznego zakresu, chyba że poprzez uprzywilejowane metody obiektu. W JavaScript, każda metoda zdefiniowana wewnątrz zakresu zamknięcia jest uprzywilejowana. Na przykład:

Zagraj z tym w JSBin. (Nie widzisz żadnego wyjścia? Skopiuj i wklej ten HTML do okienka HTML.)

W powyższym przykładzie, metoda `.get()` jest zdefiniowana wewnątrz zakresu `getSecret()`, co daje jej dostęp do wszelkich zmiennych z `getSecret()`, i czyni ją metodą uprzywilejowaną. W tym przypadku, parametr, `secret`.

Obiekty nie są jedynym sposobem na produkcję prywatności danych. Zamknięcia mogą być również używane do tworzenia funkcji stateful, których wartości zwracane mogą być zależne od ich wewnętrznego stanu, np.:

const secret = msg => () => msg;

Dostępne na JSBin. (Nie widzisz żadnego wyjścia? Skopiuj i wklej ten HTML do okienka HTML.)

W programowaniu funkcjonalnym, domknięcia są często używane do częściowego zastosowania & currying. Wymaga to kilku definicji:

Aplikacja: Proces stosowania funkcji do jej argumentów w celu uzyskania wartości zwracanej.

Partial Application: Proces stosowania funkcji do niektórych z jej argumentów. Częściowo zastosowana funkcja zostaje zwrócona do późniejszego użycia. Częściowe zastosowanie naprawia (częściowo stosuje funkcję do) jeden lub więcej argumentów wewnątrz zwracanej funkcji, a zwracana funkcja pobiera pozostałe parametry jako argumenty w celu zakończenia zastosowania funkcji.

Częściowe zastosowanie wykorzystuje zakres domknięcia w celu ustalenia parametrów. Można napisać funkcję generyczną, która będzie częściowo stosować argumenty do funkcji docelowej. Będzie ona miała następujący podpis:

partialApply(targetFunction: Function, ...fixedArgs: Any) =>
functionWithFewerParams(...remainingArgs: Any)

Jeśli potrzebujesz pomocy w odczytaniu powyższego podpisu, sprawdź Rtype: Reading Function Signatures.

Przyjmie ona funkcję, która przyjmuje dowolną liczbę argumentów, a następnie argumenty, które chcemy częściowo zastosować do funkcji, i zwróci funkcję, która przyjmie pozostałe argumenty.

Pomocny będzie przykład. Powiedzmy, że masz funkcję, która dodaje dwie liczby:

const add = (a, b) => a + b;

Teraz chcesz mieć funkcję, która dodaje 10 do dowolnej liczby. Nazwiemy ją `add10()`. Wynikiem działania funkcji `add10(5)` powinno być `15`. Nasza funkcja `partialApply()` może sprawić, że tak się stanie:

const add10 = partialApply(add, 10);
add10(5);

W tym przykładzie, argument `10` staje się stałym parametrem zapamiętanym wewnątrz zakresu zamknięcia `add10()`.

Przyjrzyjmy się możliwej implementacji `partialApply()`:

Dostępne na JSBin. (Nie widzisz żadnych danych wyjściowych? Skopiuj i wklej ten HTML do okienka HTML.)

Jak widzisz, zwraca on po prostu funkcję, która zachowuje dostęp do argumentów `fixedArgs`, które zostały przekazane do funkcji `partialApply()`.

Twoja kolej

Ten post ma towarzyszący post wideo i zadania praktyczne dla członków EricElliottJS.com. Jeśli jesteś już członkiem, zaloguj się i ćwicz teraz.

Jeśli nie jesteś członkiem, zarejestruj się już dziś.

Poznaj serię

  • Co to jest zamknięcie?
  • Jaka jest różnica między klasą a prototypowym dziedziczeniem?
  • Co to jest czysta funkcja?
  • What is Function Composition?
  • What is Functional Programming?
  • What is a Promise?
  • Soft Skills

Updates:
July 2019 – Clarified intro to explain why answering this question wrong could cost you a job or a lot of money in salary.

Dodaj komentarz

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