customelements.define()强制要求标签名含破折号,因html规范需区分自定义元素与未来原生标签;不带破折号会抛domexception;注册须早于dom解析,否则需manual upgrade。

customElements.define() 为什么强制要求标签名带破折号
因为浏览器必须无歧义地区分用户定义元素和未来可能被标准化的原生标签。不带破折号的名(如 mybutton、tabs)调用 customElements.define() 会直接抛出 DOMException: The element name is not valid,这不是兼容性问题,是 HTML 规范层硬约束。
常见错误包括:
- 复制示例时漏掉破折号,把
my-greeting写成mygreeting - 误用下划线(
my_button)或驼峰(myGreeting)——二者均不合法 - 注册前已在 HTML 中使用该标签,导致未升级(unupgraded)状态,组件逻辑不触发
如何模拟“命名空间”效果而不依赖 xmlns
xmlns 在 HTML 文档中基本无效:Chrome/Firefox/Safari/Edge 全部忽略;qz:ss 这类写法不会被识别为自定义元素,document.querySelector('qz:ss') 查不到,CSS 中写 qz\:ss { color: red; } 也不生效(除非用 application/xhtml+xml MIME type 加载,但这已脱离 HTML 实际使用场景)。
真正可行的方式只有两个:
- 团队协作约定统一前缀,如
shop-button、dash-modal,前缀需简短、全小写、项目唯一,避免myapp_v2_button这类易变长名 - 注册时机必须早于 DOM 解析完成;否则已存在的节点需手动调用
customElements.upgrade()
class/id 与自定义标签名别混用
自定义标签名(如 my-button)和 class/id 是不同层级的抽象:my-button 是全局注册的 DOM 类型,而 class="my-button" 只是一个样式标识符,两者语义完全不同。
容易踩的坑:
- 把自定义标签当 class 用:
<div class="my-button"> → 实际应写 <code><my-button></my-button>,否则不会触发组件逻辑 - 给自定义标签加冗余 class:
<my-button class="btn-primary"></my-button>→ 可以,但btn-primary是样式类,和my-button的功能无关 - 在 JS 中用
document.querySelector('.my-button')查不到组件实例,除非你真给它加了那个 class;正确方式是document.querySelector('my-button') - 把注册脚本放在
中并加defer,或确保它在开头立即执行 - 若无法控制加载顺序,注册后立刻调用
customElements.upgrade(document.body) - 不要在
connectedCallback中靠this.localName.includes('shop-')做分支逻辑——这破坏封装,也使组件无法复用
注册时机比语法更关键
最常踩的坑不是写错标签名,而是执行顺序错:脚本加载晚于 HTML 解析,导致 customElements.define() 执行时,页面里已经存在 <shop-button></shop-button> 节点,它们处于 unupgraded 状态,connectedCallback 不触发,生命周期钩子全部失效。
解决办法很实际:
真正容易被忽略的是:一旦组件被插入 DOM 后才注册,就只能靠 upgrade 补救;而 upgrade 不会重放 attributeChangedCallback,初始属性值可能丢失。











