Замыкания — 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-проблема.»