自定义元素构造函数中的延迟 setAttribute 调用导致 DOM 错误。这是一个错误吗?
Deferred setAttribute call in Custom Element constructor causes DOM error. Is it a bug?
这是 fiddle 在 Chrome 72 和 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 中的错误是:
Uncaught DOMException: Failed to construct 'CustomElement': The result must not have attributes
在 Firefox 中,错误是:
NotSupportedError: Operation is not supported
如果您评论 setAttribute
调用,错误将在两种浏览器中消失。
以下示例说明了在连接元素之前更改属性,这表明它可以用宏任务完成,但(不公平地)不能用微任务完成:
(working fiddle 以下片段)
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)
})
在第一个示例中,我没有在构造函数中设置属性,而是推迟到未来的微任务。那么为什么浏览器会抱怨呢?如果这是按照规范设计的,那么规范是否有 "design bug"?为什么我们不能这样做?
根据以下答案,我不明白为什么 需要 设置此限制。无论是否存在此浏览器引擎限制,糟糕的开发人员仍然会搞得一团糟。
IMO,让开发人员决定(并记录)他们的自定义元素如何工作。
如果我们能够在构造函数中或构造函数之后的微任务中设置属性,是否存在一些浏览器无法克服的技术限制?
spec提到这个:
This is true even if the work is done inside a constructor-initiated microtask, as a microtask checkpoint can occur immediately after construction.
根据 spec 在构造函数中有一些事情绝对不能做:
When authoring custom element constructors, authors are bound by the following conformance requirements:
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.
A return statement must not appear anywhere inside the constructor body, unless it is a simple early-return (return or return this).
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.
The element must not gain any attributes or children, as this violates the expectations of consumers who use the createElement or createElementNS methods.
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.
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
立即得到解决,因此仍在构造函数中。
如果您将代码更改为:
customElements.define('test-element', class extends HTMLElement {
constructor() {
super()
setTimeout(() => {
this.setAttribute('foo', 'bar')
}, 100)
}
})
<test-element>test</test-element>
那么它就可以工作了,因为 setTimeout
让你脱离了构造函数。
这是 fiddle 在 Chrome 72 和 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 中的错误是:
Uncaught DOMException: Failed to construct 'CustomElement': The result must not have attributes
在 Firefox 中,错误是:
NotSupportedError: Operation is not supported
如果您评论 setAttribute
调用,错误将在两种浏览器中消失。
以下示例说明了在连接元素之前更改属性,这表明它可以用宏任务完成,但(不公平地)不能用微任务完成:
(working fiddle 以下片段)
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)
})
在第一个示例中,我没有在构造函数中设置属性,而是推迟到未来的微任务。那么为什么浏览器会抱怨呢?如果这是按照规范设计的,那么规范是否有 "design bug"?为什么我们不能这样做?
根据以下答案,我不明白为什么 需要 设置此限制。无论是否存在此浏览器引擎限制,糟糕的开发人员仍然会搞得一团糟。
IMO,让开发人员决定(并记录)他们的自定义元素如何工作。
如果我们能够在构造函数中或构造函数之后的微任务中设置属性,是否存在一些浏览器无法克服的技术限制?
spec提到这个:
This is true even if the work is done inside a constructor-initiated microtask, as a microtask checkpoint can occur immediately after construction.
根据 spec 在构造函数中有一些事情绝对不能做:
When authoring custom element constructors, authors are bound by the following conformance requirements:
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.
A return statement must not appear anywhere inside the constructor body, unless it is a simple early-return (return or return this).
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.
The element must not gain any attributes or children, as this violates the expectations of consumers who use the createElement or createElementNS methods.
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.
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
立即得到解决,因此仍在构造函数中。
如果您将代码更改为:
customElements.define('test-element', class extends HTMLElement {
constructor() {
super()
setTimeout(() => {
this.setAttribute('foo', 'bar')
}, 100)
}
})
<test-element>test</test-element>
那么它就可以工作了,因为 setTimeout
让你脱离了构造函数。