将JavaScript文件置于头部时,无法构造"CustomElement"错误

19 javascript html5 custom-element

我有一个自定义元素定义如下:

class SquareLetter extends HTMLElement {
    constructor() {
        super();
        this.className = getRandomColor();
    }
}
customElements.define("square-letter", SquareLetter);
Run Code Online (Sandbox Code Playgroud)

当JavaScript文件包含在HTML <head>标记中时,Chrome控制台会报告此错误:

未捕获的DOMException:无法构造"CustomElement":结果必须没有属性

但是当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>
Run Code Online (Sandbox Code Playgroud)

小智 32

错误是正确的,并且在两种情况下都可能发生.您很幸运,因为某些自定义元素的当前实现不会强制执行此要求.

自定义元素的构造函数不应该读取或写入其DOM.它不应该创建子元素或修改属性.这项工作需要在以后完成,通常在一个connectedCallback()方法中完成(尽管connectedCallback()如果元素被删除并重新添加到DOM中,可以多次调用,因此您可能需要检查这个,或者撤消a中的更改disconnectedCallback()).

引用WHATWG HTML规范,强调我的:

§4.13.2自定义元素构造函数的要求:

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

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

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

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

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

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

  • 通常,应尽可能推迟connectedCallback工作 - 尤其是涉及获取资源或渲染的工作.但是,请注意,connectedCallback可以多次调用,因此任何真正一次性的初始化工作都需要一个防护来阻止它运行两次.

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

在元素创建期间,直接或间接检查其中一些要求,如果不遵循它们,将导致无法通过解析器或DOM API实例化的自定义元素.即使工作在构造函数启动的微任务中完成,也是如此,因为微任务检查点可以在构造之后立即发生.

将脚本移动到DOM中的元素之后,会导致现有元素完成"升级"过程.当脚本在元素之前时,元素将经历标准构造过程.这种差异显然导致错误不会出现在所有情况下,但这是一个实现细节,可能会改变.


Max*_*888 9

在大多数情况下,问题在于您尝试创建一个元素,该元素在首次添加到 DOM 时会神奇地具有属性,这不是 HTML 元素的预期行为。想一想:

const div = document.createElement("div");
document.body.append(div);
Run Code Online (Sandbox Code Playgroud)

对于 div 和所有其他元素类型,您永远不会创建一个已经具有<div class="my-class"></div>. 类和所有其他属性始终在使用 创建元素后添加.createElement(),例如:

const div = document.createElement("div");
div.className = "my-class";
document.body.append(div);
Run Code Online (Sandbox Code Playgroud)

.createElement()永远不会创建已经具有类等属性的元素。这同样适用于自定义元素。因此,如果:

const myElement = document.createElement("my-element");
document.body.append(myElement);
Run Code Online (Sandbox Code Playgroud)

添加了一个 DOM 元素,例如<my-element class="unexpected-attribute"></my-element>. 您不应该向实际的自定义元素添加类等属性。如果您想添加属性和样式,您应该将子元素附加到您的元素(在 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);
Run Code Online (Sandbox Code Playgroud)

作为网络组件:

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);
Run Code Online (Sandbox Code Playgroud)