在JavaScript中能否继承仅拥有私有构造函数的类?
在多数编程语言中,继承拥有私有构造函数的类是不可行的——因为子类必须调用父类的构造函数(比如Java中,暂不考虑特殊反例)。
JavaScript本身没有原生支持私有构造函数,但存在模拟私有构造函数的常用模式。我原本以为这类模拟私有构造的类也会有相同限制:无法被继承。
HTMLVideoElement就是一个典型例子:直接调用new HTMLVideoElement()会抛出Illegal constructor异常(在Firefox 105、Chrome 106中测试验证),看起来完全符合“私有构造函数类”的特征。
但当我创建它的子类并注册为自定义元素后,情况发生了变化:
class MyVideo extends HTMLVideoElement { constructor() { super(); } } customElements.define('my-video', MyVideo, { extends: "video"})
此时调用new MyVideo()不会抛出异常,可以正常实例化。
我既对这个可行的结果感到惊喜,又担心如果依赖该行为,未来浏览器是否会修改它——毕竟JS目前没有原生私有构造函数,相关行为的规范可能不够明确。
我的测试显示答案是“可以继承并实例化”,但理论上我觉得应该“不可以”。想请教:这是预期行为吗?未来能否安全依赖这个特性?
补充说明:只有先通过customElements.define注册子类后,new MyVideo()才不会抛异常;如果跳过注册直接调用构造函数,依然会抛出异常(已在Firefox和Chrome中验证)。
这是符合规范的预期行为,而且未来可以安全依赖——因为这是自定义元素标准和原生元素构造机制共同规定的行为,并非浏览器的临时实现。
1. 为什么HTMLVideoElement不能直接实例化?
HTMLVideoElement这类原生元素的构造函数属于受限制的构造函数,并非传统意义上的“私有构造函数”。浏览器限制直接调用它的原因是:原生元素的实例化需要绑定DOM环境的上下文,直接new无法提供正确的初始化上下文(比如元素的文档关联、内部状态初始化等)。
2. 自定义元素注册为什么能解除这个限制?
当你通过customElements.define注册继承自原生元素的自定义类时,浏览器会为这个子类构造函数注入特殊的初始化逻辑:
- 注册后,子类的
super()调用不再直接调用原生HTMLVideoElement的构造函数,而是通过浏览器提供的内部接口完成原生元素的实例化,确保上下文正确。 - 规范明确要求:对于自定义内置元素(即继承自原生HTML元素的自定义元素),注册后必须允许通过
new实例化,同时保证实例符合原生元素的所有特性。
3. 和普通模拟私有构造函数的类的区别
普通JS模拟私有构造的模式(比如在构造函数里判断调用者,非允许的情况抛出错误),是开发者自己实现的逻辑,没有浏览器的特殊支持。而HTMLVideoElement的限制是浏览器层面的,自定义元素标准专门为继承这类原生元素的场景做了适配,所以两者的机制完全不同。
4. 关于未来兼容性
这个行为已经被纳入自定义元素v1规范,目前所有支持自定义元素的浏览器(Chrome、Firefox、Edge、Safari)都一致实现了该特性,而且规范没有计划修改这一逻辑。只要你遵循自定义元素的注册流程,这个行为就是稳定的,可以放心依赖。
内容的提问来源于stack exchange,提问作者Sebastian

