For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/year/2022/proposal-class-static-block.md.
  • 简体中文
  • Class Static Block S4

    中文标题:类静态初始化块

    提案概览
    提案速览

    该提案在类体中引入了静态初始化块,允许在类定义求值期间执行任意语句,从而在类内部实现 try/catch 逻辑和多字段初始化,并拥有对私有状态的特权访问。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    ECMAScript 类静态初始化块

    static 块提供了一种机制,在类定义求值期间执行额外的静态初始化。

    这并非旨在替代公共字段,因为它们为静态分析工具提供了有用信息,并且是装饰器的有效目标。相反,它旨在扩展现有用例,并支持该提案当前未处理的新用例。

    状态

    阶段: 4
    提案发起人: Ron Buckton (@rbuckton)

    有关该提案的详细状态,请参见下面的 TODO

    作者

    • Ron Buckton (@rbuckton)

    动机

    当前关于静态字段和静态私有字段的提案提供了一种在 ClassDefinitionEvaluation 期间对类的静态部分进行逐字段初始化的机制,但有些情况不容易覆盖。例如,如果需要在初始化期间求值语句(如 try..catch),或从单个值设置两个字段,则必须在类定义之外执行该逻辑。

    // 不使用静态块:
    class C {
      static x = ...;
      static y;
      static z;
    }
    
    try {
      const obj = doSomethingWith(C.x);
      C.y = obj.y
      C.z = obj.z;
    }
    catch {
      C.y = ...;
      C.z = ...;
    }
    
    // 使用静态块:
    class C {
      static x = ...;
      static y;
      static z;
      static {
        try {
          const obj = doSomethingWith(this.x);
          this.y = obj.y;
          this.z = obj.z;
        }
        catch {
          this.y = ...;
          this.z = ...;
        }
      }
    }

    此外,在某些情况下,具有实例私有字段的类与在同一作用域中声明的另一个类或函数之间需要进行信息共享。

    静态块提供了在类声明的上下文中求值语句的机会,并可特权访问私有状态(无论是实例私有还是静态私有):

    let getX;
    
    export class C {
      #x
      constructor(x) {
        this.#x = { data: x };
      }
    
      static {
        // getX 拥有对 #x 的特权访问权限
        getX = (obj) => obj.#x;
      }
    }
    
    export function readXData(obj) {
      return getX(obj).data;
    }

    与“私有声明”的关系

    提案:https://github.com/tc39/proposal-private-declarations

    “私有声明”提案也旨在解决两个类之间的特权访问问题,其做法是将私有名称从类声明中提升到封闭作用域中。虽然在这方面有一些重叠,但私有声明并不能解决多步静态初始化的问题,除非纯粹为了初始化而将私有名称暴露给外部作用域:

    // 使用私有声明
    private #z; // 仅为了声明后初始化而暴露
    class C {
      static y;
      static outer #z;
    }
    const obj = ...;
    C.y = obj.y;
    C.#z = obj.z;
    
    // 使用静态块
    class C {
      static y;
      static #z; // 不暴露在类外部
      static {
        const obj = ...;
        this.y = obj.y;
        this.#z = obj.z;
      }
    }

    此外,私有声明暴露的私有名称可能允许对共享私有状态进行读写访问,而只读访问可能是更可取的。要使用私有声明解决此问题,需要额外的复杂性(尽管 static{} 也有类似的成本):

    // 使用私有声明
    private #zRead;
    class C {
      #z = ...; // 只能在类内部写入
      get #zRead() { return this.#z; } // 需要包装器以确保只读访问
    }
    
    // 使用静态块
    let zRead;
    class C {
      #z = ...; // 只能在类内部写入
      static { zRead = obj => obj.#z; } // 需要回调以确保只读访问
    }

    然而,从长远来看,这两个提案并不妨碍彼此同时使用:

    private #shared;
    class C {
      static outer #shared;
      static #local;
      static {
        const obj = ...;
        this.#shared = obj.shared;
        this.#local = obj.local;
      }
    }
    class D {
      method() {
        C.#shared; // 允许
        C.#local; // 不允许访问
      }
    }

    先例

    语法

    class C {
      static {
        // 语句
      }
    }

    语义

    • static {} 初始化块创建一个新的词法作用域(例如,varfunction 和块级声明都限于该 static {} 初始化块内部。此词法作用域嵌套在类体的词法作用域中(授予该类实例私有状态的特权访问权限)。
    • 类在其类体中可以有任意数量的 static {} 初始化块。
    • static {} 初始化块按照文档顺序与静态字段初始化器交错求值。
    • static {} 初始化块不能有装饰器(相反,您会装饰类本身)。
    • 求值时,static {} 初始化块的 this 接收者是类的构造函数对象(与静态字段初始化器相同)。
    • static {} 初始化块内引用 arguments语法错误
    • static {} 初始化块内包含 SuperCall(即 super())是语法错误
    • static {} 初始化块可以包含 SuperProperty 引用,以访问或调用基类上可能已被包含 static {} 初始化块的派生类覆盖的静态成员。
    • 在调试器和异常跟踪中,static {} 初始化块应表示为独立的堆栈帧。

    示例

    // “友元”访问(同一模块)
    let A, B;
    {
      let friendA;
    
      A = class A {
        #x;
    
        static {
            friendA = {
              getX(obj) { return obj.#x },
              setX(obj, value) { obj.#x = value }
            };
        }
      };
    
      B = class B {
        constructor(a) {
          const x = friendA.getX(a); // 允许
          friendA.setX(a, x); // 允许
        }
      };
    }

    参考

    TODO

    以下是推进 TC39 提案流程 各阶段所需的高级任务列表:

    阶段 1 进入标准

    • 确定将推进该提案的“提案发起人”。
    • 散文概述问题或需求以及解决方案的大致形态。
    • 说明性示例用法。
    • 高级 API

    阶段 2 进入标准

    阶段 3 进入标准

    阶段 4 进入标准

    有关阶段 4 标准的最新信息,请查看:#48