【问题标题】:Why is a new HTMLElement() prohibited?为什么禁止使用 new HTMLElement()?
【发布时间】:2023-03-30 21:53:01
【问题描述】:

我想知道为什么new HTMLElement() 和其他人(例如new HTMLDivElement())会抛出非法构造函数错误。

我想要这样的代码:

// 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);
     

但是,上面的代码不亚于只是一个愿望,因为上面的代码抛出了非法构造函数错误。

我认为有两种解决方法:(1) 使用 $ref (2) 使用自定义元素。请让我展示一下。

(1) 使用 $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);

这样,我可以做一个正方形,但不方便,因为我不能直接访问 div(=$ref)。我需要先通过“方形”对象。 (我想说的是appendChild(square)appendChild(square.$ref)之间的区别)因为new HTMLDivElement()是不可能的,所以我别无选择,只能使用一个像包装器一样的对象。

(2) 使用(自治)自定义元素——也使用 $ref。

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);

在这种情况下,new Square(5) 因为非法构造函数错误而无法完成,所以我必须使用data-len 自定义属性和connectedCallback() 以便在附加square 时将data-len 应用于$ref.style到 DOM。

当然我可以同时使用这两种方式,但我仍然想知道为什么 new HTMLElement() 被禁止。有什么历史原因吗?

另外,让我想知道的是,即使new HTMLElement() 抛出非法构造函数错误,document.createElement() 如何(在内部)创建一个新的 HTMLElement 实例?根据the first answer of this questionHTMLElement.constructor() 抛出异常,而不是创建对象。那么当我调用document.createElement('div')HTMLDivElement是如何被实例化并返回给我的呢?

另外,我看到an answer 使用Object.create() 方法创建HTMLElement 的实例。它工作并返回HTMLElement 实例,但似乎HTMLElement 实例无法正常工作。例如,

const test = Object.create(HTMLDivElement.prototype, {});

test.textContent = 'test'; // illegal invocation

由于某种原因,我可能会遇到非法调用错误。这是为什么呢?

【问题讨论】:

  • 是的,您可能打算使用document.createElement 而不是扩展内置类。
  • 我找到了关于此错误消息的答案stackoverflow.com/q/41521812/14032355
  • 使Square 扩展HTMLDivElement,然后是customElements.define('custom-square', Square, {extends: 'div'});
  • 因为 DOM 元素实际上不仅仅是 JS 并且不在同一个“领域”中,因为一个元素需要一个 ownerDocument,因为......可能还有其他好的原因。

标签: javascript dom


【解决方案1】:

简单的答案是,这正是目前 DOM 标准中定义 HTMLDivElementHTMLElement 的方式。

要了解更多信息,请先查看 MDN 上 HTMLElement 的原型链:

EventTarget ← 节点 ← 元素 ← HTMLElement

我们可以在DOM Living Standard 中找到有关每个接口的更多信息。特别是对于Node,它说:

Node 是所有节点都使用的抽象接口。您无法获得它的直接实例。

然后规范说:

每个节点都有一个关联的节点文档,在创建时设置,即文档。

这就解释了为什么我们需要使用document.createElement()来创建一个HTML元素;原型链中带有Node 的接口必须与文档相关联。

之所以选择document.createElement('script') 而不是new HTMLSciptElement(document) 之类的东西,可能是因为历史设计决策以及与似乎更倾向于工厂方法的其他DOM 的一致性。

没有什么可以阻止作者将来使Node 可构造,但现在不存在的原因可能是因为没有需求。有趣的是,原型链的下一个步骤是EventTarget,它最近被更改为可构造的。它没有与Node 相同的文档关联要求。

【讨论】:

  • 谢谢,您的回答对我帮助很大。如果你不介意我问,我想再问一个问题。由于我是初学者,我不太了解 Web IDL 中看似使用的一些术语,例如“接口”。似乎“接口”是“类”之类的东西,但我想知道“抽象接口”是什么,为什么不能直接实例化Node。谢谢。
  • 对于接口的定义,您可以进一步回到 ECMAScript 规范,参见 Common Iteration Interfaces,其中说 “接口是一组属性键,其关联值与特定规范匹配“。 “抽象”数据类型是计算和数学中更通用的术语,但在这里意味着接口不是构造函数,因此您不能创建它的实例。
猜你喜欢
  • 2013-05-11
  • 2019-10-09
  • 2013-03-29
  • 2015-10-31
  • 2011-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多