Powrót do strony głównej

Zamknięcia JS: środowisko leksykalne i przykłady

Zamknięcia w JavaScript zapewniają dostęp wewnętrznej funkcji do zewnętrznych zmiennych po zakończeniu zewnętrznej. Materiał omawia teorię z obliczeń lambda, praktyki w pętlach, emulację OOP i optymalizację silników. Przykłady kodu dla middle/senior programistów.

Jak działają zamknięcia w JS: kod i pułapki pętli
Advertisement 728x90

Domknięcia w JavaScript: mechanizm środowiska leksykalnego i praktyczne zastosowania

Domknięcie w JavaScript to kombinacja funkcji i jej środowiska leksykalnego, w której funkcja wewnętrzna zachowuje dostęp do zmiennych z zewnętrznego zakresu widoczności nawet po zakończeniu działania funkcji zewnętrznej. Zapewnia to hermetyzację stanu i przewidywalne zachowanie w kontekstach asynchronicznych.

Koncepcja wywodzi się z rachunku lambda Alonzo Churcha. Funkcje wyższego rzędu zwracają lub przyjmują inne funkcje, przechwytując wolne zmienne — te, które są używane wewnątrz, ale zadeklarowane na zewnątrz.

Przykład podstawowego licznika demonstruje zasadę:

Google AdInline article slot
function counter() {
  let count = 0
  return function(step) {
    count = count + step
    return count
  }
}

const myCounter = counter()
console.log(myCounter(1)) // 1
console.log(myCounter(1)) // 2
console.log(myCounter(1)) // 3

Funkcja wewnętrzna domyka się na count, zapobiegając odśmiecaniu. To żywe odwołanie: zmiany odzwierciedlają się w czasie rzeczywistym.

Domknięcia jako analogia OOP

Domknięcia emulują prywatne pola i publiczne API obiektów. Licznik z metodami:

function Counter(initialValue = 0) {
  let count = initialValue
  return {
    increment: function(step = 1) {
      return count += step
    },
    decrement: function(step = 1) {
      return count -= step
    },
    getValue: function() {
      return count
    },
    reset: function() {
      return count = initialValue
    }
  }
}

const myCounter = Counter(10)
console.log(myCounter.getValue()) // 10
console.log(myCounter.increment(5)) // 15

Odpowiednik na klasach z prywatnymi polami (#count) pokazuje identyczną logikę. W sklepach Pinia/Vuex domknięcia hermetyzują stan reaktywny:

Google AdInline article slot
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const initValue = 0
  function increment(step) {
    count.value += step
  }
  return { count, increment }
})

Specyfika w pętlach i iteratorach

Zachowanie domknięć w pętlach zależy od hoistingu i zakresu blokowego.

Z var — wspólna zmienna dla wszystkich iteracji:

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i) // 3, 3, 3
  }, 100)
}

Z let — oddzielne środowisko leksykalne na iterację:

Google AdInline article slot
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i) // 0, 1, 2
  }, 100)
}

Iteratory forEach, map, filter tworzą domknięcia automatycznie:

[0, 1, 2].forEach(function(i) {
  setTimeout(function() {
    console.log(i) // 0, 1, 2
  }, 100)
})

Praktyka: wywołania zwrotne i zdarzenia

W kodzie asynchronicznym domknięcia rozwiązują problem kontekstu.

Ajax z unikalnym ID:

function ajaxRequest(url) {
  let requestId = Math.random().toString(36).substring(2)
  fetch(url).then(data => {
    console.log(`Zapytanie ${requestId} zakończone:`, data)
  }).catch(error => {
    console.log(`Zapytanie ${requestId} nie powiodło się:`, error.message)
  })  
}

Obsługi zdarzeń:

function clickSetup(elemId, message) {
  const elem = document.getElementById(elemId)
  elem.addEventListener('click', function() {
    console.log(message)
  })
}

Funkcje strzałkowe działają identycznie jak zwykłe.

Optymalizacja i debugowanie

Silniki JS (V8) wykluczają nieużywane zmienne z domknięć, aby oszczędzać pamięć. W DevTools (Scope → Closure) widoczne są tylko istotne zmienne.

Kluczowe praktyki:

  • Używaj let/const w pętlach dla zakresu blokowego.
  • Domknięcia są preferowane dla prywatnego stanu bez klas.
  • W asynchronicznych wywołaniach zwrotnych ustalaj kontekst z wyprzedzeniem.
  • Monitoruj pamięć w długożyjących domknięciach.

Co jest ważne

  • Domknięcie = funkcja + środowisko leksykalne z żywymi odwołaniami do zmiennych.
  • let w pętlach tworzy domknięcia per-iteracja, var — nie.
  • Emuluje prywatne pola OOP, używane w sklepach Vuex/Pinia.
  • Zoptymalizowane przez silniki: nieużywane zmienne są wykluczane.
  • Niezbędne dla asynchronicznych wywołań zwrotnych i zdarzeń.

— Editorial Team

Advertisement 728x90

Czytaj dalej