I was reading the steps in the specification (12th edition) that occur when a class declaration or expression is used, and saw that it starts off by creating a class scope at section 15.7.7 Runtime Semantics: ClassDefinitionEvaluation:
1. Let env be the LexicalEnvironment of the running execution context.
2. Let classScope be NewDeclarativeEnvironment(env).
...
I can then see that this algorithm in the spec then creates and sets the classBinding
as a binding of the classScope
envrionment record (at steps 3a, and 19a), but other than that, I can't seem to see what else it is being used for. If that's the case, it seems like creating a classScope
would only be needed for class expressions (where the class name is available from only within the class and not in the scope its declared) and not class declarations. I'm confused as to why classScope
is created for class declarations also (15.7.8 for class declarations runs the above algorithm 15.7.7) when the class name binding is added to the surrounding scope, perhaps it's something to do with when a class uses extends
?
Even setting aside modern features added after that annual snapshot spec was published (1, 2), class declarations still needed class scope to handle the class binding correctly within the class.
You noted that a class declaration creates a binding for the class name in the scope containing the declaration, whereas a class expression doesn't:
While that's true, the inner binding in the class scope is still created, and that's important for resolving the class binding correctly within it. The constructor and methods within the class shouldn't have to rely on that external binding for the class binding, not least because that external binding is mutable:
If
method
relied on the outer binding ofExample
, it would be using the wrong thing when it didnew Example
. But thanks to class scope, it doesn't rely on that binding, it relies on the inner binding for it within the class scope (which is immutable).As I mentioned, class fields and methods make further use of the class scope, but it was needed even before they were added.
One tricky bit is this sequence you pointed out in a comment on my previous incorrect answer:
Why set the running execution context's LexicalEnvironment to classScope just to evaluate ClassHeritage?
Bergi came up with the answer to that, and it's the same answer as for
method
above: So that the class's binding is properly resolved within ClassHeritage. His wonderfully-succinct example uses a proposal (static methods) that hadn't landed in that spec yet, but it still makes the point:That shows a class expression for class
X
that extends classY
, where classY
is defined within the ClassHeritage syntax production — and refers toX
! (Is this a good idea? Probably not. But there may be very edgy edge cases.)Just for clarity, let's expand that a bit and stick to features in the spec you linked to:
So there we are, even class declarations have use for classScope, even before class fields and such were added.