自定义元素注册报错:[object HTMLElement]非有效元素名求助
咱们先拆解下这个报错的核心原因:你调用customElements.define()时,第一个参数传的是DOM元素对象,但这个方法要求第一个参数必须是带连字符的自定义元素名称字符串。当你把DOM元素塞进去时,它会自动把对象转成字符串"[object HTMLElement]",这显然不符合自定义元素的命名规则(必须包含至少一个连字符),所以直接报错了。
再看你的代码逻辑问题:
你的register方法里写了this.each(function (item) { customElements.define(item, className); }),这里的item是通过querySelectorAll拿到的<lol-foo> DOM元素,而不是元素的名称字符串。而且更关键的是:自定义元素只需要注册一次就够了——注册是告诉浏览器「这个标签名对应这个类」,之后所有该标签的元素都会自动用这个类实例化,完全不需要遍历每个元素重复注册。
下面是具体的修复方案:
1. 先给Ω构造函数加个小改动:保存选择器字符串
要拿到自定义元素的名称,我们需要把传入的选择器存下来,所以修改Constructor:
var Constructor = function (selector) { if (!selector) return; this.selector = selector; // 新增:保存选择器字符串,后面用来注册元素 if (selector === 'document') { this.elems = [document]; } else if (selector === 'window') { this.elems = [window]; } else { this.elems = document.querySelectorAll(selector); } };
2. 重构register方法的逻辑
把原来遍历元素注册的逻辑改成用保存的选择器一次性注册:
Constructor.prototype.register = function (className) { // 先做个简单校验:自定义元素名称必须带连字符 if (!this.selector.includes('-')) { console.error('自定义元素名称必须包含连字符,请检查选择器'); return this; } // 只注册一次就搞定所有该标签的元素 customElements.define(this.selector, className); return this; };
3. 修正自定义元素类的小细节
你的button类里有个小错误:super.textContent应该改成this.textContent——super指向的是HTMLElement的原型,当前实例的内容存在this上:
class Button extends HTMLElement{ // 惯例用大驼峰命名类,更易读 constructor(){ super(); const shadow = this.attachShadow({mode: 'open'}); const button = document.createElement('button'); button.style.cssText = 'display:block'; button.textContent = this.textContent; // 这里改成this shadow.appendChild(button); } }
4. 调用方式保持不变
Ω('lol-foo').register(Button); // 对应大驼峰的类名
这样修改后,页面上的<lol-foo>元素就会被自动识别为自定义元素,渲染出带Shadow DOM的按钮,内容就是你写的"Hey"了。
额外补充两个注意点:
- 自定义元素名称必须包含至少一个连字符,这是W3C规范强制要求的,用来区分内置元素和自定义元素,你的
lol-foo是符合要求的。 - 如果你的脚本是在DOM解析之后加载的,浏览器会自动对已存在的自定义元素标签进行「升级」,不需要额外处理。
内容的提问来源于stack exchange,提问作者Pranav

