先记住这个答案
自定义元素名必须以 ASCII 小写字母开头,至少包含一个连字符,且不能含 ASCII 大写字母;还要避开规范保留的少数名称。user-card 是常见的合法写法,usercard、UserCard 不行。连字符让浏览器把作者定义的标签与标准元素分开,HTML、SVG 或 MathML 将来新增标准标签时,就不会占用作者已有的名称。
- 名称须以 ASCII 小写字母开头
- 名称必须包含连字符,不能含 ASCII 大写字母
- 少数规范保留的连字符名称也不能用
- 同一注册表中不能重复注册同名元素
从注册一个卡片组件看命名
组件名不是随便写在 HTML 里的字符串。调用 customElements.define() 时,浏览器会检查名称是否合法,再把它和构造类关联起来。模板里已有同名标签时,注册后这些元素可以被升级为对应组件。
class UserCard extends HTMLElement {
connectedCallback() {
this.textContent = '用户资料' // 元素接入文档时填入内容
}
}
customElements.define('user-card', UserCard)
// HTML 中可使用 <user-card></user-card>UserCard 是 JavaScript 类名,可以按类命名习惯首字母大写;真正传给 define() 的标签名必须是小写且带连字符。把类名与标签名混为一谈,是面试里常见的失误。元素定义后,document.createElement('user-card') 和 HTML 中的 <user-card> 都可以创建它。
连字符解决什么冲突
浏览器需要区分“目前不认识、但未来可能成为标准的标签”和“开发者明确要注册的标签”。如果允许自定义元素直接叫 card,未来标准库加入 <card> 时,旧网站和新标准就会争夺同一个名字。要求连字符,相当于为作者保留一块不会被标准元素占用的命名空间。
但带连字符还不够。名称必须以 ASCII 小写字母开头,不得包含 ASCII 大写字母;规范还保留了例如 annotation-xml 和 font-face 等少数已有用途的名称。不要只靠“有横杠”判断是否合法。构建团队组件库时,通常再加统一前缀,如 acme-user-card,降低不同库在同一页面内注册同名元素的概率。
命名错误和重复注册不是一回事
如果传入的名字不符合有效自定义元素名规则,define() 会因名字无效抛异常。如果名字合法,但同一注册表已经注册过这个名字,或构造器已被注册,也会失败;这是注册冲突,换成另一个合法名字或检查初始化代码是否重复执行。热更新环境尤其容易遇到后一种情况,先用 customElements.get('user-card') 看看是否已注册,再决定是否调用 define()。
命名合法也不自动提供原生按钮、输入框等行为。独立自定义元素继承的是 HTMLElement,若做可交互控件,还需认真处理键盘、焦点、状态、表单和无障碍语义。能直接用原生元素满足需求时,不必为了“组件化”而新造标签。
容易答错的地方
- 以为 JavaScript 类名也必须带连字符
- 类名可以是
UserCard;带连字符的要求针对注册名称和 HTML 标签名。customElements.define('user-card', UserCard)正是两种命名同时出现的正常写法。 - 以为任何带连字符的字符串都有效
- 首字符、ASCII 大写字母和保留名称仍有约束。
9-user-card不能以数字开头;user-Card含大写字母,也不是有效的自定义元素名。
面试官还会怎么问?
为什么标签已经写在 HTML 中,后注册也能工作?
浏览器解析时可先创建尚未升级的自定义元素。调用 customElements.define() 注册对应名称后,符合条件的现有元素会升级,随后运行组件的生命周期逻辑。
同名组件能在一个页面注册两次吗?
不能在同一个注册表里对同名元素重复调用 define()。应用初始化、微前端集成或热更新时,要检查是否已注册,避免重复注册异常。
可以直接继承 <button> 的所有行为吗?
那属于“自定义内置元素”的另一条路线,定义时需要声明所扩展的内置标签,使用时也不是普通的 <user-card> 写法。它的支持情况与独立自定义元素不同,实际项目要按目标浏览器验证。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。