8

class Foo {
  static v = 123;

  static bar = () => this.v;
}

console.log(Foo.bar());

I expect this code to return undefined, because arrow functions are lexically scoped, hence this must be eagerly bound to the outer scope.

Yet, it returns 123.

Why exactly does this happen?

And yep, I understand it's still stage 3, but still - why does the proposed standard behave like that? (See https://babeljs.io/docs/en/babel-plugin-transform-class-properties for another example.)

Bergi
  • 513,640
  • 108
  • 821
  • 1,164
zerkms
  • 230,357
  • 57
  • 408
  • 498
  • 2
    https://tc39.es/proposal-class-fields/#runtime-semantics-class-definition-evaluation 10.a "Set the running execution context's LexicalEnvironment to classScope." so – Yury Tarabanko Sep 17 '20 at 12:15
  • 2
    @YuryTarabanko please post as an answer – zerkms Sep 17 '20 at 12:16
  • 2
    It looks like every "field" is actually wrapped into yet a function/method: https://tc39.es/proposal-class-fields/#runtime-semantics-class-field-definition-evaluation, but I'm not 100% sure. – Felix Kling Sep 17 '20 at 12:32
  • @FelixKling it looks convincing but their `FunctionCreate(Method, formalParameterList, Initializer, lex, true, privateScope).` has `PrivateScope` and `Strict` swapped. – zerkms Sep 17 '20 at 12:36
  • 1
    Apparently those methods are then executed to create the actual properties in `DefineField`: https://tc39.es/proposal-class-fields/#sec-define-field (step 5.a) – Felix Kling Sep 17 '20 at 12:38
  • @FelixKling yep, I altered my comment - they confused the order of 2 last arguments. From your link it is `FunctionCreate(Method, formalParameterList, Initializer, lex, true, privateScope)` but should be `FunctionCreate(Method, formalParameterList, Initializer, lex, privateScope, true)` – zerkms Sep 17 '20 at 12:38
  • 1
    @zerkms: Maybe just a typo? – Felix Kling Sep 17 '20 at 12:39
  • Definitely a typo, sorry for shifting focus to this irrelevant thing. Otherwise as I just mentioned, your guess looks very convincing to me :-) – zerkms Sep 17 '20 at 12:39
  • Somehow `DefineField` doesn't seem to be used for static fields though? Need to do more digging... – Felix Kling Sep 17 '20 at 12:43
  • 1
    Ah, wrong spec. https://tc39.es/proposal-static-class-features/#sec-method-definitions-runtime-semantics-classelementevaluation -> 34.a ... not easy to follow when the relevant sections are spread over multiple specs ;) – Felix Kling Sep 17 '20 at 12:45

1 Answers1

7

tl;dr: Every class field (static or not) is internally wrapped in a method which gets invoked with the corresponding receiver (class or instance) at some point.


So, I'm not sure on some of those details *, but basically this happens:

For every field with an initializer (static or not), a function/method is created, with the initializer as its body. So this

static foo = () => this.v;

becomes something like this internally

function () { () => this.v }

That's in the proposal in step 28, which eventually leads to ClassFieldDefinitionEvaluation in this spec. The method is created in step 3.e.

The static fields (which are methods now) are then taken and called with the class object itself as receiver (i.e. the this value inside that intermediate method is set to the class object). This happens in step 34.a, which leads to DefineField in this spec. Finally the return value (in your case the arrow function) is used as value for the actual property.

Expressed as code, this is roughly what happens:

class Foo {}

Foo.v = function() { return 123; }.call(Foo);
Foo.bar = function() { return () => this.v; }.call(Foo);

*: I'm not quite clear how the intermediate method returns the value, but there is probably something that says that the last expression of the function body is returned or something.

Felix Kling
  • 705,106
  • 160
  • 1,004
  • 1,072
  • I have a feeling that this part of the proposal [is not quite final](https://github.com/tc39/proposal-static-class-features/issues/38). The function wrapping looks like a hack to me, I'd hope they polish this up before stage 4. – Bergi Sep 20 '20 at 11:03