0

I am aware that in many languages inheriting from a class that has only private constructors is simply not possible, because the subclass needs to call the superclass constructors (e.g. in Java -- well, at least when neglecting the counter-example of the accepted answer of the link).

Private constructors are not supported directly in JavaScript, but there is a common pattern for how to simulate them. (see also this SO-question on private constructors in JavaScript). So for classes with such private constructors I was expecting the same constraint, i.e. not being able to inherit from the class. I have a particular example in mind, namely the HTMLVideoElement. This class throws Illegal constructor exceptions, if one tries to construct it via new HTMLVideoElement() (at least with recent Firefox (105) and Chrome (106) browsers). I don't know the native code behind this class, but this looks to me exactly like a class with private constructors in the sense I mentioned. However, if I build a simple subclass

class MyVideo extends HTMLVideoElement {
    constructor() {
        super();
    }
}

then I can create an instance of this subclass via new MyVideo() without getting any exception. On the one side I'm happy about this, but on the other side I'm suspicious that if I rely on this behaviour, it might later be changed by the browsers (as strictly speaking there are no private constructors yet in the language, I guess there are no specs yet how they should behave). So my own test with the above subclass suggests that the answer to my question is in fact 'yes', while I think that it actually should be 'no'. Does anybody have more insight into this? Is it wanted behavior? Can I rely on it for the future?

Edit: For people who want to reproduce my claim that I can call new MyVideo() without getting an exception, I have to add the observation that this is only true if I previously assigned the class to a custom element via customElements.define('my-video', MyVideo, { extends: "video"}) . If I omit this step and try to call the constructor, I do get the exception (again tested in Firefox and Chrome). Sorry for the initially misleading description, but I realized this subtle difference only now.

Sebastian
  • 365
  • 3
  • 17
  • 1
    See https://stackoverflow.com/a/22795464/294949 – danh Nov 05 '22 at 20:14
  • @danh thanks for the link! Interesting, seems to contradict my observation ... – Sebastian Nov 05 '22 at 20:17
  • 1
    @danh But somehow I doubt that the accepted answer of this link is correct in saying "You cant subclass most of the DOM api". With [custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#customized_built-in_elements) one should be able to subclass quite a lot of the DOM api, otherwise this construct would be pointless ... – Sebastian Nov 05 '22 at 20:25
  • I think you're right. It seems like the recipe in that doc is worth a try. – danh Nov 05 '22 at 20:35
  • 1
    @Sebastian When using web components, it is still recommended to instantiate them using `document.createElement('my-video')`, not directly as `new MyVideo`. So while the latter appears to work, I wouldn't rely on this. – Bergi Nov 05 '22 at 21:37
  • @Bergi thanks, this seems to be a reasonable thing to do. I guess that for customized built-in elements this needs to be slightly modified to `document.createElement("video", {is: "my-video"})`. And it turns out that this call in fact also calls the constructor in MyVideo, as it should. But I guess that the latter could simply be the default constructor or one otherwise should turn it private oneself, so that it is only called by document.createElement and not directly ... – Sebastian Nov 06 '22 at 09:13

1 Answers1

0

Let me divide the answer into two parts, the first resolving my confusion about the HTMLVideoElement and the second answering more generically the question in the title:

1: I was under the wrong impression that the absence of an accessible constructor is special for HTMLVideoElement (simply because for audio there exists an accessible constructor new Audio(url)), but the link to this question about subclassing a div-element provided by danh in the comments reminded me that almost all HTMLElements don't have a directly accessible constructor. Nevertheless since the introduction of custom html elements it is certainly intended to subclass many if not all of the native elements. As it is possible (though probably not recommended) in my example to call the subclass constructor of MyVideo directly, the only reasonable explanation is, that my assumption that the super-class constructors were "private" was wrong and that one has to think of them to be something like "protected", i.e. accessible for subclasses (taking into account the observation in the edit of the question, access to the constructor seems to be a bit more restrictive than just "protected", checking also if the class is assigned to a DOM-Element). So if the constructor apparently is not really private, the question would reduce to "how do I know which protected constructors are available or better which arguments are supposed to be passed to the super-constructor, if the latter is not documented?". For the examples at hand (various HTMLElements) Bergi pointed out in the comments that one should use document.createElement() to create them. Explicitly in my example it would be document.createElement("video", {is: "my-video"}) (see here for the documentation of createElement) where my-video is a customized built-in element defined with the class MyVideo. When calling now createElement(...) one can convince oneself in a debugger that the constructor in MyVideo is called without any arguments. In a more general case it would be wise to either simply don't write any constructor (so that the default constructor is used calling super with all arguments), or if one really needs to add something, doing it in the same way as the default constructor (see here for the documentation of in particular the default constructor):

constructor(...args) {
  super(...args);
  // do something additional here
}

2: So far this hasn't answered the actual question of the title, so the generic case where the superclass really has only private constructors in the sense of the pattern of simulated private constructors. It turns out that even then it can be possible to subclass them, but only if the superclass explicitly allows to do so in the creator-method. To show this, let me slightly modify the code of the above link:

class PrivateConstructor {
  static #isInternalConstructing = false;
  constructor() {
    if (!PrivateConstructor.#isInternalConstructing) {
      throw new TypeError("PrivateConstructor is not constructable");
    }
  }

  static create() {
    PrivateConstructor.#isInternalConstructing = true;
    // following 2 lines are the only change to the class Code of the reference
    const SubClass = this; // this might be either PrivateConstructor or a subclass thereof
    const instance = new SubClass(); 
    PrivateConstructor.#isInternalConstructing = false;
    return instance;
  }
}

class Sub extends PrivateConstructor {}

new PrivateConstructor(); // TypeError: PrivateConstructor is not constructable
PrivateConstructor.create(); // creates a PrivateConstructor instance

new Sub(); // TypeError
Sub.create(); // creates a Sub instance

The last line works to create an instance of Sub, because it is not the subclass Sub creating its own instance, but it is calling the static method of the superclass PrivateConstructor which is then creating an instance of the subclass. So it is the superclass accessing its own private constructor. For the HtmlVideoElement the situation was different, because it was possible to directly call the subclass-constructor new MyVideo(), showing that the super-constructor can't really be private, but rather protected.

Sebastian
  • 365
  • 3
  • 17