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ę:
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:
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ę:
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/constw 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.
letw 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
Brak komentarzy.