我有一个自定义元素定义如下:
class SquareLetter extends HTMLElement {
constructor() {
super();
this.className = getRandomColor();
}
}
customElements.define("square-letter", SquareLetter);
当JavaScript文件包含在HTML <head>
标记中时,Chrome控制台会报告此错误:
未捕获的DOMException:无法构造“CustomElement”:结果必须没有属性
但是当</body>
结束标记之前包含JavaScript文件时,一切正常。什么原因?
<head>
<script src="js/SquareLetter.js"></script> <!-- here -->
</head>
<body>
<square-letter>A</square-letter>
<script src="js/SquareLetter.js"></script> <!-- or here -->
</body>
错误是正确的,并且在两种情况下都可能发生。您正变得“幸运”,因为某些自定义元素的当前实现不会强制执行此要求。
自定义元素的构造函数不应该读取或写入其DOM。它不应该创建子元素或修改属性。这项工作需要在以后完成,通常使用connectedCallback()
方法(尽管请注意,如果删除元素并将其重新添加到DOM中,可以多次调用connectedCallback()
,因此您可能需要检查这个,或者撤消更改disconnectedCallback()
)。
引用WHATWG HTML规范,强调我的:
§ 4.13.2 Requirements for custom element constructors:
在创作自定义元素构造函数时,作者受以下一致性要求的约束:
- 对
super()
的无参数调用必须是构造函数体中的第一个语句,以便在运行任何其他代码之前建立正确的原型链和此值。- return语句不能出现在构造函数体内的任何位置,除非它是一个简单的早期返回(返回或返回此)。
- 构造函数不得使用
document.write()
或document.open()
方法。- 不得检查元素的属性和子元素,因为在非升级的情况下不会出现任何属性和子元素,并且依赖于升级会使元素变得不可用。
- 该元素不得获得任何属性或子女,因为这违反了使用
createElement
或createElementNS
方法的消费者的期望。- 一般来说,工作应该尽可能地推迟到
connectedCallback
- 特别是涉及获取资源或渲染的工作。但是,请注意connectedCallback
可以被调用多次,因此任何真正一次性的初始化工作都需要一个防护来阻止它运行两次。- 通常,构造函数应该用于设置初始状态和默认值,以及设置事件侦听器和可能的影子根。
在元素创建期间,直接或间接检查其中一些要求,如果不遵循它们,将导致无法通过解析器或DOM API实例化的自定义元素。即使工作在构造函数启动的微任务中完成,也是如此,因为微任务检查点可以在构造之后立即发生。
将脚本移动到DOM中的元素之后,会导致现有元素完成“升级”过程。当脚本在元素之前时,元素将经历标准构造过程。这种差异显然导致错误不会出现在所有情况下,但这是一个实现细节,可能会改变。
元素尚未加载,因此无法更改,将元素加载到元素下方意味着可以更改它