当前位置: 代码迷 >> JavaScript >> 自定义元素构造函数中的延迟 setAttribute 调用导致 DOM 错误。 这是一个错误吗?
  详细解决方案

自定义元素构造函数中的延迟 setAttribute 调用导致 DOM 错误。 这是一个错误吗?

热度:39   发布时间:2023-06-05 10:28:56.0

这是在 Chrome 72 和 Firefox 63 中显示控制台错误的小提琴:

代码是:

 <script> customElements.define('test-element', class extends HTMLElement { constructor() { super() Promise.resolve().then(() => { this.setAttribute('foo', 'bar') }) } }) </script> <test-element>test</test-element>

在 Chrome 中,错误是:

Uncaught DOMException: Failed to construct 'CustomElement': The result must not have attributes

在 Firefox 中,错误是:

NotSupportedError: Operation is not supported

如果您对setAttribute调用进行注释,则两个浏览器中的错误都会消失。

以下示例说明了在连接元素之前更改属性,这表明它可以通过宏任务完成,但(不公平地)不能通过微任务完成:

(为下面的片段)

 customElements.define('test-element', class extends HTMLElement { constructor() { super() setTimeout(() => { this.setAttribute('foo', 'bar') }) } connectedCallback() { console.log('foo attribute:', this.getAttribute('foo')) } }) const el = document.createElement('test-element') console.log('no foo attribute:', el.getAttribute('foo')) setTimeout(() => { document.body.appendChild(el) })

在第一个示例中,我没有在构造函数中设置属性,而是推迟到未来的微任务。 那么为什么浏览器会抱怨呢? 如果这是按照规范设计的,那么规范是否存在“设计错误”? 为什么我们不能这样做?

根据下面的答案,我不明白为什么需要设置此限制。 无论是否存在这种浏览器引擎限制,糟糕的开发人员仍然可能会弄得一团糟。

IMO,让开发人员决定(并记录)他们的自定义元素如何工作。

如果我们能够在构造函数或构造函数之后的微任务中设置属性,是否存在浏览器无法克服的一些技术限制?

根据,在构造函数中你绝对不能做某些事情:

在创作自定义元素构造函数时,作者受到以下一致性要求的约束:

  • 对 super() 的无参数调用必须是构造函数主体中的第一条语句,以在运行任何进一步代码之前建立正确的原型链和此值。

  • return 语句不能出现在构造函数体内的任何地方,除非它是一个简单的提前返回(返回或返回 this)。

  • 构造函数不得使用 document.write() 或 document.open() 方法。

  • 不得检查元素的属性和子元素,因为在非升级情况下,不会出现任何元素,并且依赖升级会降低元素的可用性。

  • 元素不得获得任何属性或子元素,因为这违反了使用 createElement 或 createElementNS 方法的消费者的期望。

  • 一般来说,工作应该尽可能地推迟到 connectedCallback 上——尤其是涉及获取资源或渲染的工作。 但是,请注意 connectedCallback 可以被多次调用,因此任何真正一次性的初始化工作都需要一个保护来防止它运行两次。

  • 通常,构造函数应该用于设置初始状态和默认值,以及设置事件侦听器和可能的影子根。

在元素创建过程中直接或间接检查其中一些要求,如果不遵循这些要求,将导致无法通过解析器或 DOM API 实例化的自定义元素。

您的示例的问题在于Promise会立即解决,因此仍在构造函数中。

如果您将代码更改为:

 customElements.define('test-element', class extends HTMLElement { constructor() { super() setTimeout(() => { this.setAttribute('foo', 'bar') }, 100) } })
 <test-element>test</test-element>

然后它起作用了,因为setTimeout使您脱离了构造函数。

提到了这一点:

即使工作是在构造函数启动的微任务内部完成也是如此,因为微任务检查点可以在构造后立即发生。

  相关解决方案