在Custom Element构造函数中延迟setAttribute调用会导致DOM错误.是虫子吗? [英] Deferred setAttribute call in Custom Element constructor causes DOM error. Is it a bug?

查看:83
本文介绍了在Custom Element构造函数中延迟setAttribute调用会导致DOM错误.是虫子吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个小工具,显示了Chrome 72和Firefox 63中的控制台错误:

Here's a fiddle showing an error in console in Chrome 72 and Firefox 63:

https://jsfiddle.net/jr2z1ms3/1/

代码是:

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

在Chrome中,错误是:

In Chrome the error is:

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

在Firefox中,错误是:

In Firefox the error is:

NotSupportedError: Operation is not supported

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

If you comment the setAttribute call, the error goes away in both browsers.

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

The following example illustrates changing attributes before an element is connected, which shows that it can be done with macrotasks, yet (unfairly) not with microtasks:

(工作小提琴以下代码段)

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

在第一个示例中,我没有在构造函数中设置属性,而是顺应了将来的微任务.那么,为什么浏览器会抱怨呢?如果这是按照规范的目的,那么规范是否存在设计错误"?为什么我们不能做到这一点?

In the first example I am not setting the attribute in the constructor, I am deferring to a future microtask. So why are the browsers complaining? If this is intended as per spec, then does the spec have a "design bug"? Why should we not be able to do this?

根据下面的答案,我不明白为什么要 实施此限制.无论是否设置了浏览器引擎限制,不良的开发人员仍然会惹上麻烦.

based on answers below, I don't see why this limitation needs to be in place. A bad developer can still make a mess with or without this browser-engine limitation in place.

IMO,让开发人员决定(并记录)其自定义元素的工作方式.

IMO, let devs decide (and document) how their custom elements work.

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

Is there some technical limitation that the browser otherwise can't overcome if we were to be able to set attributes in the contructor or a microtask after the constructor?

推荐答案

根据

According to the spec there are certain things you must never do in the constructor:

创作自定义元素构造函数时,作者必须遵守以下符合性要求:

When authoring custom element constructors, authors are bound by the following conformance requirements:

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

  • A parameter-less call to super() must be the first statement in the constructor body, to establish the correct prototype chain and this value before any further code is run.

return语句一定不能出现在构造函数体内的任何地方,除非它是一个简单的早期返回(返回或返回此语句).

A return statement must not appear anywhere inside the constructor body, unless it is a simple early-return (return or return this).

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

The constructor must not use the document.write() or document.open() methods.

不得检查元素的属性和子元素,因为在非升级情况下,将不存在该元素和子元素,并且依靠升级会降低元素的可用性.

The element's attributes and children must not be inspected, as in the non-upgrade case none will be present, and relying on upgrades makes the element less usable.

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

The element must not gain any attributes or children, as this violates the expectations of consumers who use the createElement or createElementNS methods.

通常,应将工作尽可能地推迟到connectedCallback进行,尤其是涉及获取资源或渲染的工作.但是,请注意,connectedCallback可以被调用多次,因此,任何真正一次性的初始化工作都需要保护措施,以防止其运行两次.

In general, work should be deferred to connectedCallback as much as possible—especially work involving fetching resources or rendering. However, note that connectedCallback can be called more than once, so any initialization work that is truly one-time will need a guard to prevent it from running twice.

通常,应使用构造函数来设置初始状态和默认值,并设置事件监听器和可能的影子根.

In general, the constructor should be used to set up initial state and default values, and to set up event listeners and possibly a shadow root.

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

Several of these requirements are checked during element creation, either directly or indirectly, and failing to follow them will result in a custom element that cannot be instantiated by the parser or DOM APIs.

您的示例出现的问题是, Promise 立即得到解决,因此仍在构造函数中.

The problem with your example is that the Promise is resolved immediately and is, thus, still in the constructor.

如果您将代码更改为此:

If you change your code to this:

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

<test-element>test</test-element>

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

Then it works because the setTimeout gets you out of the constructor.

这篇关于在Custom Element构造函数中延迟setAttribute调用会导致DOM错误.是虫子吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆