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

Может ли вложенная функция получить доступ к переменной в которую ее вложили?

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

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

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

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

Замыкания (Closures) в JavaScript

Это один из самых важных вопросов в JavaScript, который показывает понимание scope и как работает язык. Коротко: да, может. Давайте разберемся подробно.

Краткий ответ

Вложенная функция имеет доступ к переменным во вмещающей функции благодаря замыканиям (closures).

function outer() {
  const message = 'Hello'; // переменная во вмещающей функции
  
  function inner() {
    console.log(message); // inner МОЖЕТ получить доступ к message
  }
  
  inner(); // Вывод: "Hello"
  return inner;
}

const fn = outer();
fn(); // "Hello" - даже после того как outer закончилась!

Что такое Closure (Замыкание)?

Closure - это функция, которая имеет доступ к переменным из своего внешнего scope, даже после того как внешняя функция завершила выполнение.

Визуально:

Global Scope
├── outer
    ├── Local Scope of outer
    │   ├── message = 'Hello'
    │   ├── inner (замыкает message)
    │   └── return inner
    └── После выполнения: outer закончилась
        Но inner все еще имеет доступ к message!

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

Шаг 1: Переменные в scope

function outer(x) {         // x - параметр функции
  const y = 10;            // локальная переменная
  
  function inner() {
    const z = 20;          // локальная переменная inner
    return x + y + z;      // доступ к x (outer) и y (outer)
  }
  
  return inner();
}

const result = outer(5);   // result = 5 + 10 + 20 = 35

Шаг 2: Сохранение ссылки на функцию

function outer(x) {
  const y = 10;
  
  function inner() {
    return x + y;
  }
  
  return inner; // возвращаем ФУНКЦИЮ, не результат вызова!
}

const fn = outer(5);  // fn теперь содержит inner
fn();                 // 15 - inner все еще может доступ к x и y!

// Даже после того как outer() закончила
// inner помнит значения x и y

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

Пример 1: Счетчик (частый case на собеседованиях)

function createCounter() {
  let count = 0; // эта переменная "запирается" в closure
  
  return function increment() {
    count++;     // имеет доступ к count
    return count;
  };
}

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

// count не видна глобально, только через counter
// Это называется инкапсуляция

Пример 2: Factory функция

function createPerson(name) {
  const firstName = name;
  
  return {
    getName() {
      return firstName; // closure доступ к firstName
    },
    setName(newName) {
      firstName = newName; // изменяем encapsulated переменную
    }
  };
}

const person = createPerson('John');
console.log(person.getName()); // "John"
person.setName('Jane');
console.log(person.getName()); // "Jane"

Пример 3: Callback функции

function setupButtonClicks() {
  const clickCount = 0; // локальная переменная
  
  document.getElementById('btn').addEventListener('click', function() {
    clickCount++;  // этот callback имеет closure доступ к clickCount
    console.log('Clicks:', clickCount);
  });
}

setupButtonClicks();
// Каждый клик может инкрементить clickCount

Scope Chain (цепочка scope)

Как JavaScript ищет переменные:

const global = 'global';

function outer() {
  const outer_var = 'outer';
  
  function middle() {
    const middle_var = 'middle';
    
    function inner() {
      const inner_var = 'inner';
      
      // Когда inner ищет переменную, она проверяет:
      // 1. Свой scope (inner_var)
      // 2. Scope middle (middle_var)
      // 3. Scope outer (outer_var)
      // 4. Global scope (global)
      
      console.log(global);     // Found in global scope
      console.log(outer_var);  // Found in outer scope
      console.log(middle_var); // Found in middle scope
      console.log(inner_var);  // Found in inner scope
    }
    
    return inner;
  }
  
  return middle();
}

const fn = outer();
fn(); // Все логируется корректно

Проблемы с Closure (часто на собеседованиях)

Ошибка 1: Loop closure problem

// НЕПРАВИЛЬНО
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // все выведут 3
  }, 1000);
}
// Выведет: 3, 3, 3
// Потому что все callbacks делят один и тот же i

// ПРАВИЛЬНО - Вариант 1: использовать let
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 0, 1, 2
  }, 1000);
}
// let создает новый scope для каждой итерации

// ПРАВИЛЬНО - Вариант 2: IIFE (Immediately Invoked Function Expression)
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j); // 0, 1, 2
    }, 1000);
  })(i);
}
// IIFE создает новый scope и "ловит" текущее значение i

Ошибка 2: Утечка памяти

function createBigArray() {
  const bigArray = new Array(1000000).fill('data'); // много памяти
  
  return function() {
    return bigArray.length; // closure держит ссылку на bigArray
  };
}

const fn = createBigArray();
// bigArray никогда не будет очищена из памяти
// пока существует fn (垃圾сборщик не может ее удалить)

React и Closures

Очень важно для React разработчиков:

function Counter() {
  const [count, setCount] = useState(0);
  
  // Handler имеет closure доступ к count и setCount
  const handleClick = () => {
    setCount(count + 1);
  };
  
  return <button onClick={handleClick}>Count: {count}</button>;
}

// ПРОБЛЕМА: Stale closure
function BadTimer() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count); // STALE VALUE!
      // count всегда будет 0
    }, 1000);
    
    return () => clearInterval(timer);
  }, []); // пустой dependency array - плохо!
  
  return <div>Count: {count}</div>;
}

// РЕШЕНИЕ: Правильный dependency array
function GoodTimer() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count); // правильное значение
    }, 1000);
    
    return () => clearInterval(timer);
  }, [count]); // добавляем count в зависимости
}

Визуализация Closure

function outer(a) {
  return function inner(b) {
    return a + b; // inner закрывает a
  };
}

const add5 = outer(5);

// Что происходит:
// 1. outer(5) создает inner и "запирает" a=5
// 2. inner возвращается
// 3. add5 теперь содержит inner с "запертым" a=5
// 4. add5(3) вызывает inner(3), который использует "запертое" a=5

console.log(add5(3));  // 8 (5 + 3)
console.log(add5(10)); // 15 (5 + 10)

Тест на понимание

Что выведет этот код?

const functions = [];

for (var i = 0; i < 3; i++) {
  functions.push(function() {
    return i;
  });
}

console.log(functions[0]()); // ?
console.log(functions[1]()); // ?
console.log(functions[2]()); // ?

Ответ: 3, 3, 3

Почему? Все функции делят один и тот же i, и к моменту вызова i = 3.

Решение: использовать let

for (let i = 0; i < 3; i++) { // let вместо var
  functions.push(function() {
    return i;
  });
}
// Теперь выведет: 0, 1, 2

Заключение

Ключевые моменты:

  1. Вложенная функция имеет доступ к переменным внешней функции
  2. Это называется closure - функция "замыкает" доступ к переменным
  3. Closure сохраняется даже после выполнения внешней функции
  4. Это основа для паттернов: счетчики, factory функции, приватные переменные
  5. В React: closure важна для hooks - useEffect зависит от правильного понимания

Это ОБЯЗАТЕЛЬНАЯ тема для любого JavaScript разработчика!