W tym wpisie będę chciał przedstawić jedno z trudniejszych pojęć dla początkujących programistów JavaScript jakim są domknięcia (ang. closures).
Zgodnie z definicją Domknięcie jest funkcją skojarzoną z odwołującym się do niej środowiskiem. Krótko mówiąc jest to stworzenie funkcji wewnątrz funkcji. W jakim celu? Wewnętrzna funkcja ma dostęp do zmiennych własnych oraz rodzica i są one niezależne od zmiennych zewnętrznych.
Najlepiej będzie to zrozumieć na przykładzie. Najpierw omówmy pokrótce zasięg zmiennych
Zasięg zmiennych
Weźmy pod uwagę poniższy kod:
var name = "Kamil"; var age = 35; function changeData(){ var name = "Piotr"; age = 25; } changeData(); console.log(name); // "Kamil" console.log(age); // 35
Konsola wydrukuje nam „Kamil” a następnie 35. W funkcji changeData została zdefiniowana zmienna name która jest widoczna tylko w danej funkcji. Obrazuje to też poniższy przykład
function hello(name){ var greeting = "Hello, " + name; return greeting; } console.log(hello('Kamil')); // Hello, Kamil console.log(greeting); // Uncaught ReferenceError: greeting is not defined
Zmienna greeting jest zdefiniowana wewnątrz funkcji hello i tylko ona ma do niej dostęp. Po wykonaniu funkcji hello nie mamy już do niej dostępu.
Domknięcia
Tutaj pomogą nam domknięcia:
function hello(name){ var greeting = "Cześć " + name; var sayHello = function(){ var welcome = greeting + ", jak się masz?"; return welcome; }; return sayHello; } var sayHelloPiotr = hello("Piotr"); console.log(sayHelloPiotr()); // Cześć Piotr, jak się masz? console.log(sayHelloPiotr()); // Cześć Piotr, jak się masz? console.log(sayHelloPiotr()); // Cześć Piotr, jak się masz?
Funkcja sayHello w tym przykładzie to właśnie domknięcie. Ma ona dostęp do zmiennej greeting z funkcji zewnętrznej hello oraz oczywiście do własnej zmiennej lokalnej welcome.
Jak widzieliśmy w poprzednim przykładzie normalnie po wykonaniu funkcji jej zmienne wewnętrzne są niszczone i nie ma do nich dostępu. W tym przypadku po wykonaniu funkcji hello() jej zmienna greeting nie jest niszczona i funkcja sayHelloPiotr() ma do niej dostęp.
Przykład licznika
Rozważmy inny przykład z funkcją licznika:
var licznik = (function() { var ile = 0; return { dodaj: function() { ile++; }, odejmij: function() { ile--; }, value: function() { return ile; } }; }()) console.log(licznik.value()); // 0 licznik.dodaj(); console.log(licznik.value()); // 1 licznik.dodaj(); console.log(licznik.value()); // 2 licznik.odejmij(); console.log(licznik.value()); // 1 console.log(licznik.ile); // undefined console.log(ile); // Uncaught ReferenceError: ile is not defined
Definiujemy tutaj funkcję licznik ze zmienną wewnętrzną ile oraz metodami: dodaj, odejmij i value
Widzimy poniżej wykorzystanie praktyczne licznika. Zmienna ile cały czas istnieje i ma swoją wartość. Ostatnie dwie linie pokazują że nie mamy dostępu z zewnątrz do zmiennej wewnętrznej ile.
Domknięcia i setTimeout
Przeanalizujmy jeszcze jeden przykład który początkującym programistom może sprawić problem:
for(var i = 0; i < 3; i++){ setTimeout(function(){ console.log(i); }, 100); }
Co zostanie wyświetlone w konsole? Możesz sprawdzić:) Wyświetlą się 3 trójki. Dlaczego nie 0, 1, 2 ?
Zmienna i jest w tym przypadku zmienną globalną i w momencie gdy 3 razy po 100 ms wykona się funkcja wewnątrz setTimeout będzie ona miała wartość 3.
Co zrobić w tej sytuacji?
Możemy tutaj wykorzystać domknięcia i funkcji IIFE (Immediately-Invoked Function Expression):
for (var i = 0; i < 3; i++){ (function (e){ setTimeout(function (){ console.log(e); }, 100); })(i); }
W tym przypadku funkcja (function (e){ ma własny zakres zmiennych i za każdym razem przekazujemy do niej właściwą wartość zmiennej i.
Przy okazji inne rozwiązanie to nie korzystać z domknięć ale zamiast definiować zmienną globalną słowem kluczowym var wykorzystać zmienną zdefiniowaną przez let:
for(let i = 0; i < 3; i++){
setTimeout(function(){
console.log(i);
}, 100);
}
Podsumowanie
Podsumowując domknięcia (ang. closures) w JavaScript to mechanizm tworzenia zmiennych prywatnych z ograniczonym dostępem z możliwością odwoływania się do nich.
Mam nadzieję że wpis ten ułatwił zrozumienie tego tematu. Jeśli masz pytania lub uważasz, że coś powinno zostać poprawione to pisz w komentarzach!