Замыкания и области видимости — это темы, которые часто кажутся новичкам магией. На самом деле всё логично и даже элегантно. Эта статья поможет вам разобраться, как JavaScript управляет переменными и почему замыкания — мощный инструмент, а не страшный зверь.

Digital-студия WNDER

Что такое область видимости и с чем её едят

Область видимости (scope) — это правила, по которым JavaScript решает, где переменная доступна. Представьте, что переменные живут в комнатах. В своей комнате вы видите всё, что в ней есть. Но из коридора не видно, что лежит в закрытой комнате. Так же и с переменными: одни доступны везде, другие — только в определённом блоке кода.

В JavaScript есть несколько типов областей видимости:

  • Глобальная — переменная доступна из любого места программы.
  • Функциональная — переменная видна только внутри функции, где объявлена.
  • Блочная — переменная существует только внутри блока {} (появилась с let и const).

До ES6 в JavaScript не было блочной области видимости для переменных, объявленных через var. Они «поднимались» (hoisting) и могли утекать за пределы блока. Сейчас же let и const работают более предсказуемо.


// Пример: var vs let в блоке
if (true) {
  var a = 1;
  let b = 2;
}
console.log(a); // 1 — var «просачивается» наружу
console.log(b); // ReferenceError: b is not defined

Понимание областей видимости помогает избегать случайных перезаписей переменных и багов, связанных с областью действия.

Замыкания: что это и как работают

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

Вот классический пример:


function makeCounter() {
  let count = 0;
  return function() {
    count += 1;
    return count;
  };
}

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

Функция, возвращаемая из makeCounter, сохраняет доступ к переменной count даже после того, как makeCounter завершила выполнение. Это и есть замыкание: внутренняя функция «замыкает» в себе переменную из внешней функции.

Замыкания часто используются для создания приватных переменных, каррирования, обработчиков событий и многого другого.

Правило: Замыкание создаётся в момент объявления функции, а не в момент вызова. Поэтому важно, где функция была определена, а не где вызвана.

Лексическое окружение и цепочка областей видимости

Каждая функция в JavaScript имеет связанное с ней лексическое окружение (lexical environment). Это внутренняя структура, которая хранит переменные и ссылку на внешнее окружение. Когда функция ищет переменную, она сначала смотрит в своё окружение, затем во внешнее, затем во внешнее внешнего и так далее — до глобального. Эта последовательность называется цепочкой областей видимости (scope chain).

Рассмотрим пример:


const globalVar = 'global';

function outer() {
  const outerVar = 'outer';

  function inner() {
    const innerVar = 'inner';
    console.log(innerVar); // 'inner'
    console.log(outerVar); // 'outer'
    console.log(globalVar); // 'global'
  }

  inner();
}

outer();

Функция inner имеет доступ к своим переменным, затем к переменным outer, затем к глобальным. Если переменная не найдена ни в одном из окружений, возникает ошибка ReferenceError.

Цепочка областей видимости определяется в момент создания функции и не меняется. Это ключевое отличие от динамической области видимости, которая была бы основана на стеке вызовов.

Практические примеры и лайфхаки

Замыкания могут быть не только теоретической концепцией, но и полезным инструментом в повседневной разработке. Вот несколько сценариев, где они пригодятся.

Приватность данных

С помощью замыканий можно эмулировать приватные свойства объектов, недоступные извне.


function createPerson(name) {
  let _name = name;

  return {
    getName() {
      return _name;
    },
    setName(newName) {
      _name = newName;
    }
  };
}

const person = createPerson('Alice');
console.log(person.getName()); // 'Alice'
person.setName('Bob');
console.log(person.getName()); // 'Bob'
console.log(person._name); // undefined — приватная переменная

Обработчики событий с сохранением контекста

Замыкания помогают сохранять нужные значения в асинхронных вызовах.


for (let i = 1; i <= 3; i++) {
  setTimeout(function() {
    console.log(i); // 1, 2, 3 — благодаря let и замыканию
  }, i * 1000);
}

Если бы мы использовали var, то все колбэки вывели бы 4. С let каждая итерация создаёт новую переменную i, и замыкание фиксирует её значение.

Лайфхак: Используйте замыкания для создания функций с «памятью» — например, счётчиков, генераторов уникальных идентификаторов или кэширующих функций.

Сравнение областей видимости: таблица

Для наглядности сведём основные типы областей видимости в таблицу.

Тип области видимости Ключевые слова Доступность
Глобальная var, let, const (вне функций) Везде
Функциональная var, let, const (внутри функции) Только внутри функции
Блочная let, const (внутри {}) Только внутри блока
Модульная Переменные модуля Внутри модуля (если не экспортированы)

Эта таблица помогает быстро вспомнить, где какая переменная будет видна.

Что в итоге

Области видимости и замыкания — фундаментальные концепции JavaScript. Они определяют, как переменные доступны и как функции запоминают своё окружение. Понимание этих механизмов позволяет писать более надёжный, чистый и эффективный код.

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

Студия WNDER