Средний 8 мин чтения

Замыкания

Замыкание — функция, которая запоминает своё лексическое окружение и имеет доступ к переменным внешней области видимости.

closuresscopelexical environment

Что такое замыкание?

Замыкание (closure) — это функция вместе с её лексическим окружением. Функция «запоминает» переменные из области видимости, в которой была создана, и имеет к ним доступ даже после того, как внешняя функция завершила работу.

function makeCounter() {
  let count = 0  // переменная во внешней области видимости

  return function () {
    count++
    return count
  }
}

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

counter — это замыкание: оно «помнит» переменную count из makeCounter, хотя функция makeCounter уже завершила выполнение.

Лексическое окружение

Каждый раз, когда создаётся функция или блок, JavaScript создаёт лексическое окружение — объект с локальными переменными и ссылкой на внешнее окружение.

function outer() {
  const x = 10

  function inner() {
    const y = 20
    console.log(x + y) // 30 — inner видит x из outer
  }

  inner()
}

Практические применения

1. Инкапсуляция данных (приватные переменные)

function createBankAccount(initialBalance) {
  let balance = initialBalance  // "приватная" переменная

  return {
    deposit(amount) {
      balance += amount
    },
    withdraw(amount) {
      if (amount > balance) throw new Error('Недостаточно средств')
      balance -= amount
    },
    getBalance() {
      return balance
    },
  }
}

const account = createBankAccount(1000)
account.deposit(500)
account.withdraw(200)
console.log(account.getBalance()) // 1300
// account.balance → undefined (нет прямого доступа)

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

function multiplier(factor) {
  return (number) => number * factor
}

const double = multiplier(2)
const triple = multiplier(3)

console.log(double(5))  // 10
console.log(triple(5))  // 15

3. Мемоизация

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

  return function (...args) {
    const key = JSON.stringify(args)
    if (cache.has(key)) return cache.get(key)

    const result = fn.apply(this, args)
    cache.set(key, result)
    return result
  }
}

const expensiveCalc = memoize((n) => {
  console.log('Вычисляю...')
  return n * n
})

expensiveCalc(4) // "Вычисляю...", возвращает 16
expensiveCalc(4) // из кэша, возвращает 16

4. Частичное применение (partial application)

function partial(fn, ...presetArgs) {
  return function (...laterArgs) {
    return fn(...presetArgs, ...laterArgs)
  }
}

function add(a, b, c) {
  return a + b + c
}

const add10 = partial(add, 10)
console.log(add10(3, 2)) // 15

Классическая ошибка с циклом

// Проблема: все callback-и ссылаются на одну переменную i
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100)
}
// Вывод: 3, 3, 3

// Решение 1: let (блочная область видимости)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100)
}
// Вывод: 0, 1, 2

// Решение 2: IIFE (немедленно вызываемая функция)
for (var i = 0; i < 3; i++) {
  ;(function (j) {
    setTimeout(() => console.log(j), 100)
  })(i)
}
// Вывод: 0, 1, 2

Утечки памяти

Замыкания удерживают ссылки на внешние переменные — это может привести к утечкам:

function createHeavyClosure() {
  const largeData = new Array(1_000_000).fill('data')

  return function () {
    console.log(largeData.length) // largeData остаётся в памяти
  }
}

let fn = createHeavyClosure()
fn()

// Освободить память:
fn = null  // теперь замыкание и largeData могут быть удалены GC

Итог

  • Замыкание создаётся автоматически при создании функции
  • Функция «помнит» переменные из своей области видимости
  • Используется для инкапсуляции, фабрик функций, мемоизации
  • Следите за утечками памяти при работе с большими данными