Learning Book

В реальном мире

Паттерн «Модуль»

До ES Modules замыкания были единственным способом создать модуль с приватным состоянием:

const Logger = (function () {
  const logs = []

  return {
    log(message) {
      logs.push({ message, timestamp: Date.now() })
      console.log(message)
    },
    getHistory() {
      return [...logs]
    },
    clear() {
      logs.length = 0
    },
  }
})()

Logger.log('Запуск')
Logger.getHistory() // [{ message: 'Запуск', ... }]
// Logger.logs — undefined

Фабрики функций

function createValidator(minLength, maxLength) {
  return (value) => {
    if (value.length < minLength) return `Минимум ${minLength} символов`
    if (value.length > maxLength) return `Максимум ${maxLength} символов`
    return null
  }
}

const validateUsername = createValidator(3, 20)
const validatePassword = createValidator(8, 128)

validateUsername('ab')       // "Минимум 3 символов"
validatePassword('short')   // "Минимум 8 символов"

Мемоизация

function memoize(fn) {
  const cache = new Map()

  return function (arg) {
    if (cache.has(arg)) return cache.get(arg)
    const result = fn(arg)
    cache.set(arg, result)
    return result
  }
}

const fibonacci = memoize(function fib(n) {
  if (n <= 1) return n
  return fibonacci(n - 1) + fibonacci(n - 2)
})

fibonacci(40) // вычисляет
fibonacci(40) // из кэша

Каррирование

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) return fn(...args)
    return (...more) => curried(...args, ...more)
  }
}

const add = curry((a, b, c) => a + b + c)
add(1)(2)(3)  // 6
add(1, 2)(3)  // 6

React Hooks

Каждый рендер создаёт новое замыкание с текущим состоянием:

function Counter() {
  const [count, setCount] = useState(0)

  // handleClick — замыкание, захватывает count текущего рендера
  function handleClick() {
    setCount(count + 1)
  }

  return <button onClick={handleClick}>Счёт: {count}</button>
}

Упрощённая реализация useState — тоже замыкание:

function createUseState() {
  let state

  return function useState(initialValue) {
    if (state === undefined) state = initialValue
    function setState(newValue) {
      state = newValue
      // trigger re-render
    }
    return [state, setState]
  }
}

Middleware (Express / Redux)

function rateLimiter(maxRequests, windowMs) {
  const requests = new Map()

  return (req, res, next) => {
    const ip = req.ip
    const now = Date.now()
    const timestamps = requests.get(ip)?.filter(t => t > now - windowMs) ?? []

    if (timestamps.length >= maxRequests) {
      return res.status(429).json({ error: 'Слишком много запросов' })
    }

    timestamps.push(now)
    requests.set(ip, timestamps)
    next()
  }
}

// app.use(rateLimiter(100, 60_000))

Debounce / Throttle

function debounce(fn, delay) {
  let timer

  return function (...args) {
    clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}

const handleSearch = debounce(query => {
  fetch(`/api/search?q=${query}`)
}, 300)

Итого

ПаттернЧто в замыканииЗачем
МодульПриватное состояниеИнкапсуляция без классов
ФабрикаПараметры конфигурацииСпециализированные функции
МемоизацияКэш результатовОптимизация вычислений
КаррированиеНакопленные аргументыЧастичное применение
React HooksСостояние компонентаРеактивный UI
MiddlewareКонфиг + состояниеЦепочка обработчиков
DebounceТаймерОграничение частоты вызовов