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); // 35Konsola 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 definedZmienna 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 definedDefiniujemy 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!
