Uzávěry v JavaScriptu: mechanismus lexikálního prostředí a praktické využití
Uzávěr v JavaScriptu je kombinace funkce a jejího lexikálního prostředí, kde vnitřní funkce zachovává přístup k proměnným vnějšího rozsahu viditelnosti i po dokončení vnější funkce. To zajišťuje zapouzdření stavu a předvídatelné chování v asynchronních kontextech.
Koncept vznikl v lambda kalkulu Alonza Churcha. Funkce vyššího řádu vracejí nebo přijímají jiné funkce, fixujíce volné proměnné – ty, které se používají uvnitř, ale jsou deklarovány vně.
Příklad základního čítače demonstruje princip:
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
Vnitřní funkce se uzavírá na count, čímž zabraňuje sběru odpadu. Jde o živý odkaz: změny se projeví v reálném čase.
Uzávěry jako analogie OOP
Uzávěry emulují privátní pole a veřejné API objektů. Čítač s 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
Ekvivalent na třídách s privátními poli (#count) ukazuje identickou logiku. Ve storech Pinia/Vuex uzávěry zapouzdřují reaktivní stav:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const initValue = 0
function increment(step) {
count.value += step
}
return { count, increment }
})
Specifika v cyklech a iterátorech
Chování uzávěrů v cyklech závisí na hoistingu a blokovém rozsahu viditelnosti.
S var – společná proměnná pro všechny iterace:
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i) // 3, 3, 3
}, 100)
}
S let – samostatné lexikální prostředí na iteraci:
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i) // 0, 1, 2
}, 100)
}
Iterátory forEach, map, filter vytvářejí uzávěry automaticky:
[0, 1, 2].forEach(function(i) {
setTimeout(function() {
console.log(i) // 0, 1, 2
}, 100)
})
Praxe: zpětná volání a události
V asynchronním kódu uzávěry řeší problém kontextu.
Ajax s unikátním ID:
function ajaxRequest(url) {
let requestId = Math.random().toString(36).substring(2)
fetch(url).then(data => {
console.log(`Požadavek ${requestId} dokončen:`, data)
}).catch(error => {
console.log(`Požadavek ${requestId} selhal:`, error.message)
})
}
Obslužné rutiny událostí:
function clickSetup(elemId, message) {
const elem = document.getElementById(elemId)
elem.addEventListener('click', function() {
console.log(message)
})
}
Šipkové funkce fungují identicky jako běžné.
Optimalizace a ladění
Motory JS (V8) vylučují nepoužívané proměnné z uzávěrů pro úsporu paměti. V DevTools (Scope → Closure) jsou viditelné pouze relevantní proměnné.
Klíčové postupy:
- Používejte
let/constv cyklech pro blokový rozsah. - Uzávěry jsou vhodné pro privátní stav bez tříd.
- V asynchronních zpětných voláních fixujte kontext předem.
- Sledujte paměť v dlouhožijících uzávěrech.
Co je důležité
- Uzávěr = funkce + lexikální prostředí s živými odkazy na proměnné.
letv cyklech vytváří uzávěry na iteraci,varne.- Emuluje privátní pole OOP, používá se ve storech Vuex/Pinia.
- Optimalizováno motory: nepoužívané proměnné jsou vyloučeny.
- Nezbytné pro asynchronní zpětná volání a události.
— Editorial Team
Zatím žádné komentáře.