Замыкания — funarg-проблема

Замыкание в JS по спецификации — это не «функция, возвращённая из функции», а процесс связывания одного environment с другим. Решает восходящую funarg-проблему: имя в функции разрешается по месту её определения, а не вызова.

Что это / Зачем

  • В спецификации ECMAScript термин «closure» отсутствует
  • Closure — академический термин из computer science
  • В JS реализована восходящая funarg-problem: идентификаторы разрешаются по цепочке окружений вверх от определения функции
  • В Ruby/Lisp возможно нисходящее разрешение (динамическое связывание) — в JS нет

Пример funarg-проблемы

function doAdd5() {
  let a = 5;
  function doAdd() { return 1 + 2; }
  return doAdd;
}
doAdd5;
// В JS: 3 (a из outer не влияет)
// При нисходящем разрешении могло бы быть 7
  • JS: doAdd видит только a из своего окружения определения
  • Восходящее разрешение = lexical = static scoping
  • Это и есть «замыкание» как процесс

Environments в спецификации

Что объявлено Куда попадает
var Variable Environment
let, const Lexical Environment
параметры функции Lexical Environment
function f() {
  var va = 1;     // -> Variable Environment
  let vb = 2;     // -> Lexical Environment
  const vc = 3;   // -> Lexical Environment
}
  • Это два разных окружения в спеке
  • Классическое определение замыкания обычно ссылается только на lexical — отсюда путаница
  • Цепочка окружений (scope chain в жаргоне) образует механизм поиска идентификаторов

Замыкание возникает при определении

let a = 1;
function doFunc() {
  function doFunc2() { /* ... */ }
  // замыкание doFunc2 формируется только при вызове doFunc
}
  • В момент определения функции фиксируется правило разрешения имён
  • В момент вызова создаётся новое окружение для конкретного запуска
  • Объявленная в global функция уже замкнута на global environment — без всякого return

Даже блок кода — замыкание

function duet() {
  {
    let a = 1; // блок создаёт новое Lexical Environment
  }
}
  • { } с let/const по спеке создаёт новое окружение
  • Это окружение связано с окружением функции, та — с global
  • Цепочка связываний и есть процесс замыканий

V8 нарушает спеку ради скорости

  • По спеке каждый блок должен создавать своё окружение
  • V8 этого не делает — даёт уникальные имена идентификаторам блока
  • Если возвращаемая функция не использует идентификаторы родителя — V8 не сохраняет родительское окружение вообще
  • Замыкание из стрелочной функции, замыкающее простую переменную — единственная настоящая константа в JS: оптимизатор подставляет значение

Ключевые правила

  • В JS все функции — замыкания, даже если ничего не возвращают
  • Возврат функции наружу — лишь способ дольше сохранить замыкание
  • var и let/const лежат в разных окружениях по спеке
  • Замыкание формируется в момент определения, не возврата

Подводные камни

  • Термин «лексическое окружение» в JS жаргоне ≠ Lexical Environment в спеке
  • Утверждение «замыкание — это когда функция возвращена из функции» — неточное
  • V8 может вообще не создавать структуру замыкания, если она не нужна
  • const гарантирует только неизменность ссылки в блоке, не настоящую константность

🎓 Источник: Замыкания с точки зрения официальной спецификации · ⚡ AsForJS

  • 📅 2025-07-05 · YouTube · ID: RvYq-wt_GEU
  • Тезисы:
    • «Никаких замыканий в языке JavaScript не существует» — провокационный тезис: в спеке такого термина нет
    • Closure — академический термин, описывающий решение восходящей funarg-проблемы
    • var → variable environment, let/const → lexical environment (это РАЗНЫЕ окружения по спеке)
    • Замыкание есть и без return, и даже у блока с let
    • V8 не хранит окружение, если оно не используется
    • Стрелка => constValue — единственная настоящая константа в JS
  • Цитата: «Замыканием называется процесс, когда одно окружение связывается с другим окружением, что позволяет коду, находящемуся в другом окружении, находить идентификаторы в своем или в окружении, находящемся выше.»
  • Цитата: «Правильно об этом говорить как о решении funarg-проблемы, которая академическим языком называется восходящая funarg-проблема.»

См. также