Zpět na domů

Uzavření JS: lexikální prostředí a příklady

Uzavření v JavaScriptu zajišťují přístup vnitřní funkce k vnějším proměnným po dokončení vnější. Materiál rozebírá teorii z lambda-výpočtů, praxi v cyklech, emulaci OOP a optimalizaci enginů. Příklady kódu pro middle/senior vývojáře.

Jak fungují uzavření v JS: kód a pasti cykly
Advertisement 728x90

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:

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

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:

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 }
})

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:

Google AdInline article slot
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/const v 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é.
  • let v cyklech vytváří uzávěry na iteraci, var ne.
  • 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

Advertisement 728x90

Číst dál