Domknięcia w JavaScript

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!

Ta strona używa ciasteczek (cookies), dzięki którym nasz serwis może działać lepiej. Więcej informacji

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.

Close