项目作者: tc39

项目描述 :
ECMAScript class static initialization blocks
高级语言: HTML
项目地址: git://github.com/tc39/proposal-class-static-block.git
创建时间: 2018-05-11T23:30:05Z
项目社区:https://github.com/tc39/proposal-class-static-block

开源协议:BSD 3-Clause "New" or "Revised" License

下载


ECMAScript class static initialization blocks

Class static blocks provide a mechanism to perform additional static initialization during class
definition evaluation.

This is not intended as a replacement for public fields, as they provide useful information for
static analysis tools and are a valid target for decorators. Rather, this is intended to augment
existing use cases and enable new use cases not currently handled by that proposal.

Status

Stage: 4
Champion: Ron Buckton (@rbuckton)

For detailed status of this proposal see TODO, below.

Authors

Motivations

The current proposals for static fields and static private fields provide a mechanism to perform
per-field initialization of the static-side of a class during ClassDefinitionEvaluation, however
there are some cases that cannot be covered easily. For example, if you need to evaluate statements
during initialization (such as try..catch), or set two fields from a single value, you have to
perform that logic outside of the class definition.

  1. // without static blocks:
  2. class C {
  3. static x = ...;
  4. static y;
  5. static z;
  6. }
  7. try {
  8. const obj = doSomethingWith(C.x);
  9. C.y = obj.y
  10. C.z = obj.z;
  11. }
  12. catch {
  13. C.y = ...;
  14. C.z = ...;
  15. }
  16. // with static blocks:
  17. class C {
  18. static x = ...;
  19. static y;
  20. static z;
  21. static {
  22. try {
  23. const obj = doSomethingWith(this.x);
  24. this.y = obj.y;
  25. this.z = obj.z;
  26. }
  27. catch {
  28. this.y = ...;
  29. this.z = ...;
  30. }
  31. }
  32. }

In addition, there are cases where information sharing needs to occur between a class with an
instance private field and another class or function declared in the same scope.

Static blocks provide an opportunity to evaluate statements in the context of the current class
declaration, with privileged access to private state (be they instance-private or static-private):

  1. let getX;
  2. export class C {
  3. #x
  4. constructor(x) {
  5. this.#x = { data: x };
  6. }
  7. static {
  8. // getX has privileged access to #x
  9. getX = (obj) => obj.#x;
  10. }
  11. }
  12. export function readXData(obj) {
  13. return getX(obj).data;
  14. }

Relation to “Private Declarations”

Proposal: https://github.com/tc39/proposal-private-declarations

The Private Declarations proposal also intends to address the issue of privileged access between two classes, by lifting
the private name out of the class declaration and into the enclosing scope. While there is some overlap in that respect,
private declarations do not solve the issue of multi-step static initialization without potentially exposing a private
name to the outer scope purely for initialization purposes:

  1. // with private declarations
  2. private #z; // exposed purely for post-declaration initialization
  3. class C {
  4. static y;
  5. static outer #z;
  6. }
  7. const obj = ...;
  8. C.y = obj.y;
  9. C.#z = obj.z;
  10. // with static block
  11. class C {
  12. static y;
  13. static #z; // not exposed outside of class
  14. static {
  15. const obj = ...;
  16. this.y = obj.y;
  17. this.#z = obj.z;
  18. }
  19. }

In addition, Private Declarations expose a private name that potentially allows both read and write access to shared private state
when read-only access might be desireable. To work around this with private declarations requires additional complexity (though there is
a similar cost for static{} as well):

  1. // with private declarations
  2. private #zRead;
  3. class C {
  4. #z = ...; // only writable inside of the class
  5. get #zRead() { return this.#z; } // wrapper needed to ensure read-only access
  6. }
  7. // with static
  8. let zRead;
  9. class C {
  10. #z = ...; // only writable inside of the class
  11. static { zRead = obj => obj.#z; } // callback needed to ensure read-only access
  12. }

In the long run, however, there is nothing that prevents these two proposals from working side-by-side:

  1. private #shared;
  2. class C {
  3. static outer #shared;
  4. static #local;
  5. static {
  6. const obj = ...;
  7. this.#shared = obj.shared;
  8. this.#local = obj.local;
  9. }
  10. }
  11. class D {
  12. method() {
  13. C.#shared; // ok
  14. C.#local; // no access
  15. }
  16. }

Prior Art

Syntax

  1. class C {
  2. static {
  3. // statements
  4. }
  5. }

Semantics

  • A static {} initialization block creates a new lexical scope (e.g. var, function, and block-scoped
    declarations are local to the static {} initialization block. This lexical scope is nested within the lexical
    scope of the class body (granting privileged access to instance private state for the class).
  • A class may have any number of static {} initialization blocks in its class body.
  • static {} initialization blocks are evaluated in document order interleaved with static field initializers.
  • A static {} initialization block may not have decorators (instead you would decorate the class itself).
  • When evaluated, a static {} initialization block’s this receiver is the constructor object of the class
    (as with static field initializers).
  • It is a Syntax Error to reference arguments from within a static {} initialization block.
  • It is a Syntax Error to include a SuperCall (i.e., super()) from within a static {} initialization block.
  • A static {} initialization block may contain SuperProperty references as a means to access or invoke static
    members on a base class that may have been overridden by the derived class containing the static {}
    initialization block.
  • A static {} initialization block should be represented as an independent stack frame in debuggers and exception
    traces.

Examples

  1. // "friend" access (same module)
  2. let A, B;
  3. {
  4. let friendA;
  5. A = class A {
  6. #x;
  7. static {
  8. friendA = {
  9. getX(obj) { return obj.#x },
  10. setX(obj, value) { obj.#x = value }
  11. };
  12. }
  13. };
  14. B = class B {
  15. constructor(a) {
  16. const x = friendA.getX(a); // ok
  17. friendA.setX(a, x); // ok
  18. }
  19. };
  20. }

References

TODO

The following is a high-level list of tasks to progress through each stage of the TC39 proposal process:

Stage 1 Entrance Criteria

  • Identified a “champion“ who will advance the addition.
  • xProse outlining the problem or need and the general shape of a solution.
  • Illustrative examples of usage.
  • High-level API.

Stage 2 Entrance Criteria

Stage 3 Entrance Criteria

Stage 4 Entrance Criteria

For up-to-date information on Stage 4 criteria, check: #48

  • Test262 acceptance tests have been written for mainline usage scenarios and merged.
  • Two compatible implementations which pass the acceptance tests:
  • A pull request has been sent to tc39/ecma262 with the integrated spec text.
  • The ECMAScript editor has signed off on the pull request.