Reputation: 169
I'm wondering why new HTMLElement()
and others (for example new HTMLDivElement()
) throw illegal constructor error.
I want codes like this:
// create a square with a given length
class Square extends HTMLDivElement {
constructor(len) {
super();
this.style.width = `${len}px`;
this.style.height = `${len}px`;
this.style.border = '1px solid black';
}
}
const square = new Square(5);
document.body.appendChild(square);
However, the above code is no less than just a wish, because the above code throws illegal constructor error.
I think there are two workarounds: (1) using $ref (2) using custom element. Please let me show.
(1) Using $ref
class Square {
constructor(len) {
const $div = document.createElement('div');
$div.style.width = `${len}px`;
$div.style.height = `${len}px`;
$div.style.border = '1px solid black';
this.$ref = $div;
}
}
const square = new Square(5); // returns Square object
document.body.appendChild(square.$ref);
This way, I can make a square, but it is inconvenient since I can't access to the div(=$ref) directly. I need to go via 'square' object first. (What I want to say is the difference between appendChild(square)
and appendChild(square.$ref)
) Because new HTMLDivElement()
is not possible, I have no choice but to use an object which acts like a wrapper.
(2) Using (autonomous) custom element -- also uses $ref too.
class Square extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
const $div = document.createElement('div');
this.shadowRoot.appendChild($div);
this.shadowRoot.$ref = $div;
}
connectedCallback() {
this.shadowRoot.$ref.style.width = `${this.dataset.len}px`;
this.shadowRoot.$ref.style.height = `${this.dataset.len}px`;
this.shadowRoot.$ref.style.border = '1px solid black';
}
}
customElements.define('custom-square', Square);
const square = document.createElement('custom-square');
square.setAttribute('data-len', 5);
document.body.appendChild(square);
In this case, new Square(5)
cannot be done because of the illegal constructor error, so I must use data-len
custom attribute and connectedCallback()
in order to apply data-len
to $ref.style
when square
is appended to DOM.
Of course I can use both ways, but still I wonder why new HTMLElement()
is prohibited. Are there any historical reasons for that?
Also, what makes me wonder is that even though new HTMLElement()
throws illegal constructor error, how document.createElement()
can make a new instance of HTMLElement (internally)? According to the first answer of this question, HTMLElement.constructor()
throws an exception, not creating an object. Then how HTMLDivElement
is instantiated and returned to me when I call document.createElement('div')
?
In addition, I saw an answer that uses Object.create()
method to create an instance of HTMLElement
. It works and returns HTMLElement
instance, but seems the HTMLElement
instance doesn't work correctly. For example,
const test = Object.create(HTMLDivElement.prototype, {});
test.textContent = 'test'; // illegal invocation
For some reason I can get illegal invocation error. Why is that?
Upvotes: 5
Views: 1470
Reputation: 4055
The simple answer is that this is just how HTMLDivElement
and HTMLElement
are defined in the DOM standard at the moment.
To find out more, first take a look at the prototype chain for HTMLElement
on MDN:
EventTarget ← Node ← Element ← HTMLElement
We can find out more information about each of these interfaces in the DOM Living Standard. In particular, for Node
it says:
Node is an abstract interface that is used by all nodes. You cannot get a direct instance of it.
The spec then says:
Each node has an associated node document, set upon creation, that is a document.
This explains why we need to use document.createElement()
to create a HTML element; interfaces with Node
in their prototype chain must be associated with a document.
The reason why document.createElement('script')
was chosen over something like new HTMLSciptElement(document)
is probably because of historical design decisions and consistency with the rest of the DOM which seems to favor factory methods.
There is nothing to stop the authors from making Node
constructible in the future but the reason this doesn’t exist now is probably because there isn’t demand for it. It’s interesting to note that one step down the prototype chain is EventTarget
which was recently changed to be constructible. It does not have the same document association requirement as Node
.
Upvotes: 3