← Назад к вопросам

Есть опыт ли работы с замыканиями

1.0 Junior🔥 161 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Замыкания (Closures): Опыт и Практическое Применение

Да, я активно работаю с замыканиями в повседневной разработке. Это один из ключевых концептов JavaScript, без которого невозможна профессиональная работа на фронтенде.

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

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

// Простой пример
function outer() {
  const secretValue = 'секрет'; // переменная внешней функции
  
  function inner() {
    console.log(secretValue); // имеет доступ к secretValue
  }
  
  return inner;
}

const myClosure = outer();
myClosure(); // Выведет: "секрет"
// Даже хотя outer() уже завершила работу,
// inner() всё ещё имеет доступ к secretValue

Практический опыт: Фабрика функций

Counter Factory — часто встречается в собеседованиях:

function createCounter() {
  let count = 0; // приватная переменная
  
  return {
    increment: () => ++count,
    decrement: () => --count,
    getCount: () => count,
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount());  // 1

// count не доступна извне, это приватная переменная
// console.log(counter.count); // undefined

Практическое применение в React:

function useAuth() {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  
  // Замыкание над user, setUser, isLoading
  const login = useCallback(async (email: string, password: string) => {
    setIsLoading(true);
    try {
      const response = await api.login(email, password);
      setUser(response.data);
    } finally {
      setIsLoading(false);
    }
  }, []); // dependencies пусты, но login имеет доступ к setUser благодаря замыканию
  
  const logout = useCallback(() => {
    setUser(null);
  }, []);
  
  return { user, isLoading, login, logout };
}

Пример 1: Приватные переменные (как в других LanguageS)

const createBank = () => {
  let balance = 0; // полностью приватная
  
  return {
    deposit: (amount) => {
      balance += amount;
      return balance;
    },
    withdraw: (amount) => {
      if (amount <= balance) {
        balance -= amount;
        return balance;
      }
      throw new Error('Недостаточно средств');
    },
    getBalance: () => balance,
  };
};

const myBank = createBank();
myBank.deposit(1000);    // 1000
myBank.withdraw(200);    // 800
console.log(myBank.getBalance()); // 800

// Защита от изменения изне
myBank.balance = 9999; // не влияет на реальный balance
console.log(myBank.getBalance()); // всё ещё 800

Пример 2: Функции-обработчики в событиях

// На примере React
function MailList({ emails }: { emails: Email[] }) {
  const handleDelete = (emailId: string) => {
    // Замыкание над emailId
    return async () => {
      await api.deleteEmail(emailId);
      // emailId доступен в этом замыкании
    };
  };
  
  return (
    <ul>
      {emails.map((email) => (
        <li key={email.id}>
          {email.subject}
          <button onClick={handleDelete(email.id)}>
            Удалить
          </button>
        </li>
      ))}
    </ul>
  );
}

Пример 3: Debounce и Throttle

Debounce — часто используется для API запросов:

function debounce(func, delay) {
  let timeoutId; // переменная в замыкании
  
  return function debounced(...args) {
    // debounced имеет доступ к func, delay, timeoutId
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func(...args), delay);
  };
}

const debouncedSearch = debounce(async (query) => {
  const results = await api.search(query);
  // ...
}, 500);

// В input обработчике
input.addEventListener('change', debouncedSearch);

Throttle:

function throttle(func, limit) {
  let inThrottle; // переменная в замыкании
  
  return function throttled(...args) {
    if (!inThrottle) {
      func(...args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
}

const throttledScroll = throttle(() => {
  console.log('Scroll event');
}, 1000);

window.addEventListener('scroll', throttledScroll);

Пример 4: Event Listeners и Memory Leaks

Это важно для понимания утечек памяти:

function setupComponent(element: HTMLElement) {
  let clickCount = 0; // в замыкании
  
  const handleClick = () => {
    clickCount++; // имеет доступ к clickCount
    console.log(`Клик #${clickCount}`);
  };
  
  element.addEventListener('click', handleClick);
  
  // Возвращаем cleanup функцию
  return () => {
    element.removeEventListener('click', handleClick);
    // handleClick удалена, и замыкание тоже может быть удалено
  };
}

// Использование
const cleanup = setupComponent(document.querySelector('button'));

// Когда готово — очищаем
cleanup();

Пример 5: Module Pattern

const userModule = (() => {
  const users = []; // приватная переменная
  
  return {
    add: (user) => {
      users.push(user);
    },
    getAll: () => [...users], // возвращаем копию
    remove: (id) => {
      const index = users.findIndex((u) => u.id === id);
      if (index > -1) users.splice(index, 1);
    },
  };
})();

userModule.add({ id: 1, name: 'John' });
console.log(userModule.getAll()); // [{id: 1, name: 'John'}]

// users не доступна извне — только через методы
// userModule.users; // undefined

Пример 6: Цепочка вызовов с замыканиями

function add(x) {
  return function(y) {
    return x + y; // замыкание над x
  };
}

const addFive = add(5); // x = 5 в замыкании
console.log(addFive(3)); // 8 (x + y = 5 + 3)
console.log(addFive(10)); // 15 (x + y = 5 + 10)

// Или в одну строку
console.log(add(5)(3)); // 8

Частые ошибки с замыканиями

Ошибка 1: Цикл с setTimeout

// Неправильно
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // выведет 3, 3, 3
  }, 1000);
  // i в замыкании ссылается на одну и ту же переменную
}

// Правильно #1: let вместо var
for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // выведет 0, 1, 2
  }, 1000);
  // let создаёт новую область видимости для каждой итерации
}

// Правильно #2: IIFE (если нужен var)
for (var i = 0; i < 3; i++) {
  ((j) => {
    setTimeout(() => {
      console.log(j); // выведет 0, 1, 2
    }, 1000);
  })(i);
  // Создаём новое замыкание для каждого значения i
}

Ошибка 2: Утечка памяти с замыканиями

// Неправильно
function createLargeObject() {
  const largeData = new Array(1000000); // большой объект
  
  return () => {
    console.log(largeData.length); // замыкание хранит largeData
  };
}

// largeData не может быть удалена из памяти, пока существует замыкание
const callback = createLargeObject();

// Правильно: очищаем, когда не нужно
setTimeout(() => {
  callback(); // используем
  // потом очищаем
}, 1000);

На собеседовании

Полный ответ должен показать:

  • Теория — что такое замыкание и как оно работает
  • Практические примеры — где используются в реальном коде
  • React примеры — useCallback, custom hooks
  • Частые ошибки — что может пойти не так
  • Performance — как замыкания влияют на память
  • Memory Leaks — как избежать утечек
Есть опыт ли работы с замыканиями | PrepBro