// index.js
const foo = 'a'
class Animal {
constructor() {
const foo = 'b'
}
bar = foo;
}
const animal = new Animal();
Допустим, что index.js
не является модулем и существует в глобальном окружении (глобальной области видимости). Назовём это окружение GlobalEnv{}
.
При лексическом разборе и выполнении файла биндинг на const foo
будет добавлен в GlobalEnv
:
GlobalEnv{
foo: $bindingToConstantFoo$,
}
Далее будет разбор класса Animal
, который начнётся с создания нового окружения (области видимости) для него и установки ссылки на внешнее окружение. Назовём его ClassEnv{}
:
ClassEnv{
outerEnv: GlobalEnv{},
}
Это окружение устанавливается в качестве текущего контекста исполнения и происходит разбор конструктора класса. В результате этого разбора появляется замыкание в виде тела функции constructor
, а её внешнее лексическое окружение устанавливается на ClassEnv{}
:
this = ClassEnv{}
ConstructorClosure{
env: ClassEnv{},
foo: $bindingToConstantFoo$,
}
Далее выполняется некоторое кол-во абстрактных операций (имя класса, связывание прототипов и прочее), которые нам сейчас не интересны.
И почти в самом конце происходит разбор тела класса (кроме конструктора, его мы уже разобрали), в котором поочереди разбираются и добавляются в виртуальный список instanceFields
поля класса.
При этом используется лексическое окружение текущего контекста исполнения, которое сейчас установлено на ClassEnv{}
.
Поэтому, дойдя до bar = foo
, парсер будет искать переменную foo
в текущем контексте исполнения, но там ничего ещё нет, поэтому поднимаемся выше к глобальному контексту исполнения. В нём есть объявленная константа foo
, ставим биндинг на неё:
ClassEnv{
outerEnv: GlobalEnv{},
instanceFields: {
bar: $bindingToGlobalEnv.foo$,
}
}
По примерно тому же сценарию обрабатываются методы класса, и переменные резолвятся так же.
По итогу разбора класса возвращается замкнутая функция с установленным прототипом, доступом к лексическому окружению класса с объвленными полями и ссылкой на внешнюю область видимости.
Далее отрабатывает логика оператора new
.
Резюмируя вышесказанное: Из-за того, что поля класса выполняются в области видимости класса, а не функции-конструктора, у них нет доступа к её переменным.
Примерное упрощённое состояние перед вызовом new Animal()
:
GlobalEnv{
foo: $bindingToConstantFoo$,
Animal: $bindingToConstructorClosure$,
}
ClassEnv{
outerEnv: GlobalEnv{},
instanceFields: {
bar: $bindingToGlobalEnv.foo$,
}
}
ConstructorClosure{
env: ClassEnv{},
prototype: $bindingToPrototype$,
foo: $bindingToLocalConstantFoo$,
}