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/orthogonal-classes.md.
  • 简体中文
  • Orthogonal Classes S0

    中文标题:正交类成员

    提案概览
    提案速览

    该提案为类成员引入统一的语法模型,沿三个正交维度组织:可见性(公开 vs 私有)、种类(变量 vs 方法)和位置(静态、原型或实例)。它提议使用 own 表示实例成员,使用 # 表示私有字段,同时禁止某些组合,如原型私有方法。该提案是一份设计文档,提议语法变更,尚未成熟为规范。

    Note

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

    正交类成员语法

    作者:Mark S. Miller (@erights) 和 Allen Wirfs-Brock (@allenwb)

    ECMAScript 类语法:一个澄清正交关注点的提案

    摘要

    各种 不同的 提案 为新的 ClassBody 元素提出了语法和语义。然而,到目前为止,对于如何选择新的 ClassBody 扩展的语法,还没有任何指导。本文档为 ClassBody 成员元素提出一个通用的语法模型。这个模型可以用于协调现有和未来提案中的语法设计决策。

    一个“类成员”是一个对象属性或私有字段,作为 ClassBody 的一部分被定义。我们通过三个正交维度来定义类成员:

    1. 可见性:公共属性还是私有字段?
    2. 种类:变量还是方法?
    3. 位置:成员是类构造函数的一部分,原型的一部分,还是每个类实例的一部分?

    这些维度中的每一个都编码在 ClassElement 的语法结构中。可见性 通过成员名称的语法来编码:要么是 PropertyName,要么是一个 #BindingIdentifier 序列,指定一个私有字段名。种类 通过使用初始化绑定的语法或 MethodDefinition 的语法来编码。位置 通过 ClassElement 的初始关键字来编码:static 前缀表示类构造函数的成员,own 前缀表示实例对象的成员,没有关键字表示原型对象的成员。

    一个 ClassElement 要么是一个单一的 MethodDefinition,要么是一个初始化绑定列表。

    //一个厨房水槽示例
    class Foo {
      //实例成员
      own x=0, y=0;  // 两个数据属性
      own #secret;   // 一个私有字段
                     // 初始值为 undefined
      own *[Symbol.iterator](){yield this.#secret}
                     // 一个生成器方法
      own #callback(){}  //一个私有实例方法  
      //类构造函数成员               
      static #p=new Set(), q=Foo.#p;
                    // 一个私有字段和一个属性
                    // 属于类构造函数                     
      static get p(){return Foo.#p} //访问器方法     
      //原型方法                
      setCallback(f){this.#callback=f}
      constructor(s){
         this.#secret = s;
      }
    }

    这个提案最重要的部分是语法正交性的概念。关键思想是,不是任意地将语法分配给各种 ClassElement 结构,而是有一组简单的正交语法单元,代表可组合的语义概念。如果 JS 程序员理解 static# 和绑定列表语法形式的意义,那么他们就会理解 static #foo;own #bar; 的含义,即使他们没有专门学习过构造函数上的私有字段或自身的私有实例字段。正交性确实引入了一些实用性有限的语法形式。但这没关系,因为正交的一致性使这些少用的案例变得可理解。重要的是,所有允许的情况都遵循语法标记的非惊讶的正交含义。一些特殊情况的限制是可以的,但太多就会失去正交性,退化为一大包任意规则。

    细节

    非正交目标

    1. 与现有 ECMAScript 类定义语法兼容
    2. 表达私有字段和公共属性
    3. 易于理解
    4. 难以误解
    5. 初始化语法不引起与赋值混淆
    6. 允许一个注解同时注解多个字段和/或属性
    7. 表达私有方法和私有静态方法
    8. 在“浅而宽”的意义上可理解
    9. 邀请理解变得“更深更窄”
    10. 没有充分理由不切断明智的选择
    11. 避免为了表达许多明智的选择而采用临时语法的组合爆炸
    12. 保留对现有提案的投资——保持与它们相似
    13. 避免由于其他语言中使用类似语法定义类而导致的 private/public 语义混淆。

    声明式类状态的正交维度

    位置:成员是类构造函数、原型还是类实例的一部分?
    static

    如果 ClassElement 的初始关键字是 static,则该元素在类/构造函数上定义成员。这与现有对 static 的提议用法兼容。

    own

    如果 ClassElement 的初始关键字是 own,则该元素在类构造函数创建的实例对象上定义成员。这与当前在实例上初始化状态的提案不同。除了正交性的一般好处外,使用 own 相比没有 own 的这些提案具有特定优势:

    1. 语法 x = 9; 看起来像一个赋值,会被误解为赋值。语法 own x = 9; 看起来像一个变量声明和初始化,使 DefineProperty 语义不那么令人惊讶。
    2. 将多个声明分组在一起可以使它们一起被注解。然而,有几个人说他们发现 x = 9, y, z = 10; 在这个位置令人困惑。语法 own x = 9, y, z = 10; 不令人惊讶,再次因为遵循了变量声明和初始化的现有语法模式。
    3. 该语法支持在实例对象上定义方法形式。
    既不是 static 也不是 own

    在没有任何 staticown 初始关键字的情况下,该元素在原型对象上定义成员。当与 MethodDefinition 类元素一起使用时,这与现有语法兼容。

    与绑定列表形式的类元素一起使用会重现我们上面列举的 x = 9, y, z = 10; 的混淆,再加上这将在原型上初始化的进一步混淆。此外,虽然对于原型方法定义(这是最常用的 ClassElement 形式)来说,没有前置位置关键字的简洁性是有意义的,但对于很少需要的原型数据属性或原型级私有字段来说,它意义不大。因此,我们禁止没有 staticown 初始关键字的绑定列表形式的类元素。对于编写代码的人来说,这种正交性的违反可能是一个粗暴的意外。但对于阅读没有被静态拒绝的代码的人来说,没有这种违反或粗暴的意外。

    由于下面讨论的原因,我们也禁止私有方法的原型放置。

    可见性:公共属性 vs 私有字段

    在现有的私有状态提案中,前缀为 # 符号的类元素定义类实例的私有字段。现有的私有状态提案推迟了如何表达私有实例方法、私有原型方法或私有静态方法的问题。在本提案中,任何定义以 # 符号为前缀的成员名称的类元素都定义了一个私有字段。实例、原型或类/构造函数的位置根据位置关键字正交地确定。static MethodDefinition,其中 MethodDefinition 中的成员名称以 # 为前缀,是一个作为类/构造函数对象的私有字段访问的方法,即该方法是私有字段的初始值。类似地,包含 # 符号的 own MethodDefinition 是一个作为类/构造函数创建的实例对象的私有字段访问的方法。每个这样的实例对象的私有方法字段在实例初始化过程中用一个新的函数对象初始化。(或者,初始函数对象在类定义时确定,并最初由所有实例共享。这个决定属于私有状态提案的领域。)

    在类似 WeakMap 的定义私有状态的方式中,这些额外的情况以相同的方式指定:私有名称在相同的代码体范围内。但是,不是使用实例作为由这些名称命名的 weakmap 类似集合的键,键将是原型或类/构造函数。当然,实现可以通过任何与该方式在观察上无差异的方式来实现。

    种类:变量还是方法?

    每个成员要么通过 MemberDefinition 定义,要么作为成员绑定列表的元素定义。虽然两种成员通常都是可变的,但我们将通过绑定列表定义的成员特征化为“变量”,因为它们通常用于存储状态,而不是调用对象行为。

    一个 MethodDefinition 可以定义一个普通方法、一个生成器方法、一个异步函数方法、一个访问器或构造函数。访问器成员不关心成员的初始值,而是关心成员本身的性质。这仅对属性有意义。私有访问器字段没有意义,因此不允许。

    原型上的私有方法不可用

    一个“私有方法”只是一个使用方法定义初始化的私有字段。在类原型上定义私有方法的能力最初听起来像是有用的功能。然而,当检查提议的私有字段访问语义时,原型的私有方法放置结果上相当无用。基本问题是没有方便的方式引用这些方法,因为实例对象不继承访问放在原型上的私有字段的能力。考虑:

    class X {
      #helper() {};  //原型上的私有方法
      leader() {
        this.#helper(); //错误,因为实例没有 #helper 字段
        #helper();      //错误,因为这意味着与 this.#helper() 相同
        this.__proto__.#helper();  //如果在子类实例上调用则错误
        X.prototype.#helper();    //有效(假设没有重新接线)但丑陋
                      //最好使用静态私有方法:
                      //static #helper(self) {};
                      // X.#helper(this);
      }
    }

    对于大多数原型上的私有方法用例,类作用域的词汇函数声明似乎是更好的解决方案。这些将在单独的提案中提出。

    禁止的成员定义摘要

    一个成员定义要么是数据成员绑定列表的元素,要么是 MethodDefinition 形式之一。大多数方法放置和可见性的正交组合允许使用任何成员定义形式。语法上禁止的形式在下表中总结。

    位置/可见性公共属性私有字段
    own访问器方法
    static访问器方法
    (原型)数据属性数据字段
    MethodDefinition

    提议的语法

    以下是本提案引入的提议的语法变化。与本提案不直接相关的语法参数被省略,并将在最终规范文本中重新引入。

    14.5 类定义 变更
    ClassElement :
       MemberElement
       Annotation MemberElement
       ;
    

    Annotation装饰器提案定义。

    MemberElement :
       ConstructorElement
       PrototypeElement
       InstanceElement
    
    ConstructorElement :
         static MemberBindingList ;
         static MethodDefinition[Private]
    
    PrototypeElement :
         MethodDefinition
    
      InstanceElement :
         own MemberBindingList ;
         own MethodDefinition[Private]
    
     MemberBindingList :
         MemberBinding[Private]
        MemberBindingList ,  MemberBinding[Private]
    
    MemberBinding[Private] :
        PropertyName  Initializeropt
        [+Private] PrivateBindingIdentifier  Initializeropt
    
    PrivateBindingIdentifier :
        # BindingIdentifier
     
    14.3 方法定义 变更
    MethodDefinition[Private] :
       MethodName[?Private] ( UniqueFormalParameters ) { FunctionBody }
       GeneratorMethod[?Private]
       AsyncMethod[?Private]
       get PropertyName ( ) { FunctionBody }
       set PropertyName ( PropertySetParameterList ) { FunctionBody }
    
    MethodName[Private] :
        PropertyName   
        [+Private] PrivateBindingIdentifier   
    
    14.4 生成器函数定义 变更
    GeneratorMethod[Private]:
       *  MethodName[?Private] ( UniqueFormalParameters ) { GeneratorBody }
    
    14.6 异步函数定义 变更
     AsyncMethod[Private]:
       async [no LineTerminator here] MethodName[?Private] ( UniqueFormalParameters ) { AsyncFunctionBody }