customElements.define() 必须在 DOM 就绪后执行,否则自定义元素无法正确升级;constructor 仅用于初始化,DOM 操作必须放在 connectedCallback 中;attributeChangedCallback 仅响应 setAttribute 调用;Shadow DOM 中 CSS 变量不自动继承,需手动注入。

customElements.define() 必须等 DOM 就绪后才执行
脚本放在 <head> 里同步执行,而自定义标签写在 <body> 中,浏览器解析到该标签时注册还没发生——它会被当成普通未知元素丢进 DOM,后续再调用 customElements.define() 也救不回来,connectedCallback 永远不会触发。
可靠做法只有两种:
• document.addEventListener('DOMContentLoaded', () => { customElements.define('my-card', MyCard); });
• 把注册代码包在 <script defer> 里,确保 HTML 解析完成后再执行
别用 <script> 直接写在 <head> 顶部,这是最常踩的坑
constructor 只做初始化,DOM 操作全得挪到 connectedCallback
很多人在 constructor 里就查 this.shadowRoot.querySelector 或设 this.innerHTML,结果返回 null 或样式不生效。因为此时元素还没挂载,this.shadowRoot 可能为空,DOM 上下文不可用。
正确分工是:
• constructor 仅调 super()、创建 Shadow DOM(this.attachShadow({ mode: 'open' }))、声明私有字段
• 所有节点查询、事件监听、属性读取、innerHTML 设置,必须放在 connectedCallback 中
注意:connectedCallback 可能被多次调用(比如路由切换时元素移出又插入),需自行防重复,例如用 this._rendered 标记
attributeChangedCallback 不响应 JS 属性赋值,只认 setAttribute
写 el.checked = true 或 el.disabled = false 完全不会触发 attributeChangedCallback。它只对 setAttribute()、removeAttribute() 和初始 HTML 中的属性生效。
若要让 JS 属性和 HTML 属性同步(比如用户代码调 toggle.checked = true 同时更新 UI),就得手动补逻辑:
• 在类上定义 checked setter,内部调用 this.setAttribute('checked', '')
• static get observedAttributes() 必须显式列出要监听的属性名,漏一个就收不到回调
• oldValue 首次为 null,别直接用 === 判断;布尔属性的 newValue 是字符串 "true" 或 null,不是布尔值
Shadow DOM 中 CSS 变量无法穿透,必须手动注入
你在页面根元素写了 :root { --primary-color: #007bff; },但在组件 shadowRoot 的 <style> 里写 color: var(--primary-color);,结果颜色始终是 fallback 值——因为 Shadow DOM 是样式边界,var() 查找范围仅限于自身内部。
立即学习“前端免费学习笔记(深入)”;
解决方案很实际:
• 最稳妥:在 connectedCallback 中拼接 <style> 字符串并插入 shadowRoot
• 若样式复杂,可预编译为字符串常量或模板函数生成,避免运行时重复解析
• <link rel="stylesheet"> 或 @import 放在自定义元素内部基本无效,别指望它能加载成功



















