Failed to construct 'CustomElement' 错误,当 JavaScript 文件被放置在 head 中时
Failed to construct 'CustomElement' error when JavaScript file is placed in head
我有一个这样定义的自定义元素:
class SquareLetter extends HTMLElement {
constructor() {
super();
this.className = getRandomColor();
}
}
customElements.define("square-letter", SquareLetter);
当JavaScript文件包含在HTML<head>
标签中时,Chrome控制台报错:
Uncaught DOMException: Failed to construct 'CustomElement': The result must not have attributes
但是当 JavaScript 文件包含在 </body>
结束标记之前时,一切正常。什么原因?
<head>
<script src="js/SquareLetter.js"></script> <!-- here -->
</head>
<body>
<square-letter>A</square-letter>
<script src="js/SquareLetter.js"></script> <!-- or here -->
</body>
该元素尚未加载,因此无法更改,加载元素下方的脚本意味着可以更改它
错误是正确的,两种情况都可能发生。您会得到 "lucky",因为自定义元素的某些当前实现不强制执行此要求。
自定义元素的构造函数不应读取或写入其 DOM。它不应创建子元素或修改属性。该工作需要稍后完成,通常在 connectedCallback()
方法中(但请注意,如果删除元素并重新添加到 DOM,则可以多次调用 connectedCallback()
,因此您可能需要对此进行检查,或撤消 disconnectedCallback()
).
中的更改
引用 WHATWG HTML 规范,强调 我的:
§ 4.13.2 Requirements for custom element constructors:
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. This is true even if the work is done inside a constructor-initiated microtask, as a microtask checkpoint can occur immediately after construction.
当您将脚本移动到 DOM 中的元素之后时,会使现有元素经过 "upgrade" 过程。当脚本位于元素之前时,元素将执行标准构建过程。这种差异显然导致错误不会出现在所有情况下,但这是一个实现细节,可能会发生变化。
我在使用自定义组件创建元素时遇到了同样的问题。它没有修复,直到我删除了构造函数中的所有内容,除了 connectedCallback 函数的超级函数。
在大多数情况下,问题在于您尝试创建的元素在首次添加到 DOM 时会神奇地具有属性,这不是 HTML 的预期行为元素。想想:
const div = document.createElement("div");
document.body.append(div);
对于 divs 和所有其他元素类型,您将永远不会创建已经具有 等属性的 DOM 元素。 类 和所有其他属性总是在使用 .createElement()
创建元素之后添加,例如:
const div = document.createElement("div");
div.className = "random-class";
document.body.append(div);
.createElement()
永远不会创建已经具有 类 等属性的元素。这同样适用于自定义元素。
因此,如果出现以下情况,那将是意想不到的:
const myElement = document.createElement("my-element");
document.body.append(myElement);
添加了一个 DOM 元素,例如 <my-element class="unexpected-attribute"></my-element>
。
您不应该将 类 之类的属性添加到实际的自定义元素中。如果你想添加属性和样式,你应该将 children 附加到你的元素(在 web 组件的 shadowRoot 中),例如 div 你可以添加任何你想要的属性,同时离开您的实际自定义元素没有属性。
示例:
class SquareLetter extends HTMLElement {
constructor() {
super();
const div = document.createElement("div");
div.className = getRandomColor();
this.appendChild(div);
}
}
customElements.define("square-letter", SquareLetter);
作为网络组件:
class SquareLetter extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: "open"});
const div = document.createElement("div");
div.className = getRandomColor();
this.shadowRoot.append(div);
}
}
customElements.define("square-letter", SquareLetter);
我有一个这样定义的自定义元素:
class SquareLetter extends HTMLElement {
constructor() {
super();
this.className = getRandomColor();
}
}
customElements.define("square-letter", SquareLetter);
当JavaScript文件包含在HTML<head>
标签中时,Chrome控制台报错:
Uncaught DOMException: Failed to construct 'CustomElement': The result must not have attributes
但是当 JavaScript 文件包含在 </body>
结束标记之前时,一切正常。什么原因?
<head>
<script src="js/SquareLetter.js"></script> <!-- here -->
</head>
<body>
<square-letter>A</square-letter>
<script src="js/SquareLetter.js"></script> <!-- or here -->
</body>
该元素尚未加载,因此无法更改,加载元素下方的脚本意味着可以更改它
错误是正确的,两种情况都可能发生。您会得到 "lucky",因为自定义元素的某些当前实现不强制执行此要求。
自定义元素的构造函数不应读取或写入其 DOM。它不应创建子元素或修改属性。该工作需要稍后完成,通常在 connectedCallback()
方法中(但请注意,如果删除元素并重新添加到 DOM,则可以多次调用 connectedCallback()
,因此您可能需要对此进行检查,或撤消 disconnectedCallback()
).
引用 WHATWG HTML 规范,强调 我的:
§ 4.13.2 Requirements for custom element constructors:
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()
ordocument.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
orcreateElementNS
methods.In general, work should be deferred to
connectedCallback
as much as possible—especially work involving fetching resources or rendering. However, note thatconnectedCallback
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. This is true even if the work is done inside a constructor-initiated microtask, as a microtask checkpoint can occur immediately after construction.
当您将脚本移动到 DOM 中的元素之后时,会使现有元素经过 "upgrade" 过程。当脚本位于元素之前时,元素将执行标准构建过程。这种差异显然导致错误不会出现在所有情况下,但这是一个实现细节,可能会发生变化。
我在使用自定义组件创建元素时遇到了同样的问题。它没有修复,直到我删除了构造函数中的所有内容,除了 connectedCallback 函数的超级函数。
在大多数情况下,问题在于您尝试创建的元素在首次添加到 DOM 时会神奇地具有属性,这不是 HTML 的预期行为元素。想想:
const div = document.createElement("div");
document.body.append(div);
对于 divs 和所有其他元素类型,您将永远不会创建已经具有 等属性的 DOM 元素。 类 和所有其他属性总是在使用 .createElement()
创建元素之后添加,例如:
const div = document.createElement("div");
div.className = "random-class";
document.body.append(div);
.createElement()
永远不会创建已经具有 类 等属性的元素。这同样适用于自定义元素。
因此,如果出现以下情况,那将是意想不到的:
const myElement = document.createElement("my-element");
document.body.append(myElement);
添加了一个 DOM 元素,例如 <my-element class="unexpected-attribute"></my-element>
。
您不应该将 类 之类的属性添加到实际的自定义元素中。如果你想添加属性和样式,你应该将 children 附加到你的元素(在 web 组件的 shadowRoot 中),例如 div 你可以添加任何你想要的属性,同时离开您的实际自定义元素没有属性。
示例:
class SquareLetter extends HTMLElement {
constructor() {
super();
const div = document.createElement("div");
div.className = getRandomColor();
this.appendChild(div);
}
}
customElements.define("square-letter", SquareLetter);
作为网络组件:
class SquareLetter extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: "open"});
const div = document.createElement("div");
div.className = getRandomColor();
this.shadowRoot.append(div);
}
}
customElements.define("square-letter", SquareLetter);