customElements.define() 是当前唯一能真正封装结构+样式+行为且不依赖构建工具的原生方案;自定义元素名须全小写加短横线(如user-card),constructor仅初始化,DOM操作须在connectedCallback中进行,Shadow DOM默认用open模式,slot是内容分发唯一标准方式。

原生 HTML 实现代码重用,customElements.define() 是目前最可靠、浏览器支持最广的方案——不是“可选”,而是当前唯一能真正封装结构+样式+行为且不依赖构建工具的原生路径。
自定义元素名必须含短横线,否则注册直接失败
浏览器强制校验命名规则:my-button 合法,mybutton 或 MyButton 会抛出 DOMException: The element name is not valid。这不是警告,是硬性拦截。
- 命名必须全小写 + 至少一个短横线(
-),比如user-card、data-table - 不能以
html、x-开头(后者是历史遗留前缀,已废弃) - 避免使用已存在或即将标准化的标签名,如
dialog、menu,哪怕加了短横线也建议避开
constructor 里只做初始化,DOM 操作必须等 connectedCallback
在 constructor 中调用 this.attachShadow() 没问题,但任何读取属性、渲染内容、监听事件的操作,如果放在 constructor 里,大概率拿不到真实值——因为此时元素还没插入 DOM,属性可能尚未解析完毕。
-
connectedCallback是安全起点:元素被插入文档时触发,此时this.getAttribute('label')才稳定可用 - 不要在
constructor里写this.innerHTML = '...',这会污染影子根外的 DOM,也违背封装意图 - 若需响应属性变更,必须配合
static get observedAttributes()和attributeChangedCallback,否则改属性不会触发重绘
Shadow DOM 的 mode: 'open' 是默认选择,'closed' 几乎没实用场景
mode: 'closed' 看似更“私密”,但实际会让调试、自动化测试、甚至某些无障碍工具失效——因为 element.shadowRoot 返回 null,无法访问内部节点。
立即学习“前端免费学习笔记(深入)”;
- 绝大多数情况用
mode: 'open',它允许通过el.shadowRoot访问,也兼容 DevTools 检查 -
mode: 'closed'仅适合极少数强隔离需求(如第三方嵌入式 widget),且需自行暴露必要 API,开发成本陡增 - 样式隔离靠 Shadow DOM 本身即可,不需要靠
closed来“防篡改”——真要防,靠的是设计契约,不是技术锁死
slot 插槽不是可选功能,而是内容分发的唯一标准方式
想让使用者像写 <my-card><h2>标题</h2>
<p>正文</p></my-card> 这样传内容?不用 <slot></slot>,就只能靠 innerHTML 硬塞,那等于放弃 Shadow DOM 隔离,样式和事件都容易泄漏。
-
<slot></slot>是 Web Components 规范中唯一受支持的内容分发机制,浏览器原生识别并自动投影 - 带名插槽(
<slot name="footer"></slot>)配合slot="footer"属性,才能实现多区域内容注入 - 别试图用
document.querySelector在 shadowRoot 里找用户传入的节点——它们不在 shadowRoot 内,而在 light DOM,需通过slot.assignedNodes()获取
最容易被忽略的一点:自定义元素类必须在 customElements.define() 调用前完成定义,且不能重复注册;一旦注册,名字就全局唯一,后续同名调用会直接报错,不会覆盖。这意味着动态加载多个组件文件时,得自己加守卫逻辑,或者用模块化打包保证执行顺序。



















