new.initialize S1
- 阶段: Stage 1
- 状态: 进行中
- ECMAScript 版本: —
- 同步时间: 2026年8月26日
- English original · 官方仓库
该提案引入了 new.initialize(),允许类在不调用父构造函数的情况下用字段和私有方法初始化对象。它解决了动态父类查找导致错误初始化的问题,并简化了使用 Reflect.construct 或 Proxy 的模式。这是一个早期草案,尚未提交 TC39。
以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。
new.initialize()
早期草案提案,用于支持使用字段和私有方法初始化给定对象。
状态:尚未提交至 TC39;没有阶段
背景
创建 JavaScript 子类的实例是一个自底向上的过程:子类构造函数被调用,它可以返回任何它想要的对象。它“正常”的做法是调用父类构造函数,然后执行与子类相关的任何初始化。这里有一个抽象的例子:
“super 调用” super() 会根据 GetSuperConstructor 算法动态地找到父类。这个算法是一种花哨的说法,super() 调用的是 B.__proto__,无论执行 super() 时它是什么,而不是总是调用原始的 A。
因此,如果您修改原型,将调用不同的父构造函数,它不会执行原始父构造函数的操作!
您可以通过 Object.preventExtensions 或 Object.freeze 使原型链不可变来防止这种情况发生:
然而,内置子类倾向于调用原始父类,而不冻结构造函数,无论是来自 JavaScript 还是 Web 平台,正如这个问题指出的。幸运的是,JavaScript 提供了一个强大的工具来解决这个问题:Reflect.construct。使用 Reflect.construct,您可以专门调用特定的父类构造函数,并将 new.target(最低级别的子类)作为参数传递。使用这种技术,我们可以重新定义 B 如下:
这效果很好,您可以将其视为与内置类的工作方式类似。
问题
字段声明和私有方法由语言运行时隐式添加,要么在构造函数开头(对于基类),要么作为 super() 的最后一步(对于子类)。如果我们使用 Reflect.construct 确保总是调用原始父类构造函数,同时保持构造函数本身不被冻结,我们如何允许子类添加字段或私有方法?
我们如何防止 TypeError 并确保调用 C 的构造函数,同时仍然允许 D 的构造函数添加私有字段 #w?
提出的解决方案
new.initialize() 语法可用于向任何对象添加字段和私有方法,当类想要避免调用父构造函数时。类 D 可以写成如下形式:
另一个用例:带有私有的简单观察类
Proxy 可以用来观察对象。对于普通属性,可以使用 = 或 Object.defineProperty 在 Proxy 上创建属性,但私有字段和方法仅由构造函数安装。这意味着可能需要一个笨拙的“super 返回技巧”来观察带有私有字段的对象的访问。
new.initialize 可以简化这种模式,允许一个构造函数既返回 Proxy 又能添加字段。
详细语法和语义
new.initialize 仅在类的构造函数内或构造函数中的直接 eval 中在词法上被允许。它求值为一个长度为 1 的内置函数,该函数接受其单个参数并对其调用 InitializeInstanceElements 抽象操作。该参数用作返回值。
注意,只会添加包含当前构造函数的类中的字段和私有方法;继承类中的字段和私有方法不会由 new.initialize 添加(但如果您想,可以通过 Reflect.construct 生成一个包含它们的对象)。
该语法使用元属性模式,遵循 new.target 和 function.sent 的示例。
实现说明
当前和正在进行的 JS 引擎中公共和私有字段及方法的一些实现内部创建一个隐藏函数,该函数相当于 new.initialize,但将正在构造的实例作为接收者,而不是参数。new.initialize 的实现将是该函数的简单包装器。仅当构造函数包含 new.initialize 或词法包含的直接 eval 时才需要创建它,因此实践中不应有显著的内存开销。
临时解决方案
如果您想在不等待此提案通过标准化的情况下获得其好处,现在可以使用以下策略之一:
- 在基类中定义私有字段或方法(可以说这就是内置类所做的),并将子类中的私有字段替换为对属性的写入(例如,通过转译去掉它们)
- 使用 WeakMap 和 WeakSet 代替私有字段和方法,以便您可以在
Reflect.construct模式中将它们放置到位。字段和私有方法以及此提案的转译器实现可能会这样做。 - 通过
Object.preventExtensions或Object.freeze在子类构造函数上防止原型修改。
因为这些策略一开始似乎足够,尽管它们各自都有局限性,此提案不需要阻塞类字段提案。
致谢
感谢 Allen Wirfs-Brock 几年前提出这个问题,以及 Domenic Denicola 提供了用于 polyfilled 标准库功能的应用程序上下文。