JS Tower
JavaScript & TypeScriptМодуль 2: Функции

Замыкания

Как функции запоминают переменные

Цель урока

В этом уроке ты научишься:

  • Понимать, что такое замыкание
  • Использовать замыкания на практике
  • Избегать типичных ошибок

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

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

function createGreeter(greeting) {
  // greeting "замыкается" во внутренней функции
  return function(name) {
    console.log(greeting + ", " + name + "!");
  };
}

const sayHello = createGreeter("Привет");
const sayBye = createGreeter("Пока");

sayHello("Иван"); // "Привет, Иван!"
sayBye("Мария");  // "Пока, Мария!"

Как это работает

function outer() {
  let count = 0; // эта переменная "живёт" в замыкании
  
  return function inner() {
    count++;
    return count;
  };
}

const counter = outer();

console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

Ключевой момент

Каждый вызов outer() создаёт новое замыкание со своей переменной count.

const counter1 = outer();
const counter2 = outer();

console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 — отдельный счётчик

Практические примеры

Приватные переменные

function createBankAccount(initialBalance) {
  let balance = initialBalance; // приватная переменная
  
  return {
    deposit(amount) {
      balance += amount;
      return balance;
    },
    withdraw(amount) {
      if (amount > balance) {
        return "Недостаточно средств";
      }
      balance -= amount;
      return balance;
    },
    getBalance() {
      return balance;
    }
  };
}

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

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

function multiply(a) {
  return function(b) {
    return a * b;
  };
}

const double = multiply(2);
const triple = multiply(3);

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

Мемоизация

function memoize(fn) {
  const cache = {};
  
  return function(arg) {
    if (cache[arg] !== undefined) {
      console.log("Из кэша");
      return cache[arg];
    }
    
    console.log("Вычисляем");
    const result = fn(arg);
    cache[arg] = result;
    return result;
  };
}

const factorial = memoize(function(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
});

console.log(factorial(5)); // Вычисляем → 120
console.log(factorial(5)); // Из кэша → 120

Замыкание в цикле

Проблема

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 100);
}
// 3, 3, 3 (не 0, 1, 2!)

Решение 1: let

for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 100);
}
// 0, 1, 2

Решение 2: IIFE

for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j);
    }, 100);
  })(i);
}
// 0, 1, 2

Итого

  • Замыкание — функция + её лексическое окружение
  • Позволяет создавать приватные переменные
  • Каждый вызов внешней функции создаёт новое замыкание
  • Используй let в циклах для избежания проблем

Практика

Задание 1: Счётчик

Задача: Создай функцию makeCounter, которая возвращает объект с методами increment, decrement, getValue.

Запустите код для проверки
Loading...
Ваш вывод:
Ожидаемый результат:
0
1
2
1

Задание 2: Фабрика функций

Задача: Создай функцию createAdder(n), которая возвращает функцию, прибавляющую n.

Запустите код для проверки
Loading...
Ваш вывод:
Ожидаемый результат:
15
25

Задание 3: Однократный вызов

Задача: Создай функцию once(fn), которая позволяет вызвать fn только один раз.

Запустите код для проверки
Loading...
Ваш вывод:
Ожидаемый результат:
Инициализация
undefined

Проверь себя

  1. Что такое замыкание?
  2. Почему var в цикле с setTimeout выводит одно значение?
  3. Как создать приватную переменную?