Skip to content

Instantly share code, notes, and snippets.

@yesley
Last active July 30, 2020 16:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yesley/991fb28052e2743db5d03d4161df661b to your computer and use it in GitHub Desktop.
Save yesley/991fb28052e2743db5d03d4161df661b to your computer and use it in GitHub Desktop.
// 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$,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment