constructor中必须调用attachShadow(),因浏览器仅允许此时首次创建shadow root;延迟至connectedCallback会因light DOM已含子节点而报错。

constructor 里必须调用 attachShadow()
浏览器只允许在 constructor 中首次调用 this.attachShadow(),延迟到 connectedCallback 会直接报错:Failed to execute 'attachShadow' on 'Element': Shadow root cannot be created on a host which already has children。这是因为 HTML 解析器已在元素插入文档时把子节点(比如文本、<slot>)塞进了 light DOM,此时再 attach 就违反了规范。
实操建议:
-
constructor中只做三件事:调用super()、执行this.attachShadow({ mode: 'open' })、初始化默认属性或私有字段 - 不要在
constructor中读取this.textContent或操作子节点——此时 DOM 还没挂载,值为空或不可靠 - 避免写
this.innerHTML = '...',这会污染 light DOM,破坏封装性
attributeChangedCallback 不触发?先检查 observedAttributes
HTML 属性变更不会自动同步到 JS 属性,也不会触发重渲染。即使你写了 this.size = newValue,若没声明 static get observedAttributes() 返回 ['size'],attributeChangedCallback 根本不会执行。
常见陷阱:
立即学习“前端免费学习笔记(深入)”;
- 拼写必须完全一致:
'disabled'≠'Disabled',大小写敏感 - 首次渲染时已有属性(如
<my-button size="large">),attributeChangedCallback不会触发——得在connectedCallback里手动同步一次 -
oldValue和newValue永远是字符串,哪怕你传的是数字或布尔值
样式必须内联进 shadowRoot,不能靠外部 CSS 文件
Shadow DOM 天然隔离样式,外部 <link rel="stylesheet"> 或全局 CSS 规则对 shadow 内部无效。想让按钮变蓝,不能指望页面里有个 button { background: blue }。
可行方案:
- 用
<style>标签注入:shadow.innerHTML = '<style>button{...}</style><button></button>' - 或创建
style元素并 append 到 shadow:const style = document.createElement('style'); style.textContent = '...'; shadow.appendChild(style); - 不推荐用
element.style.xxx行内样式,它无法作用于 shadow 内部子元素(比如 shadow 里的button)
内容分发必须用 <slot>,别依赖 this.innerHTML
<slot> 是 Web Components 的内容分发机制,相当于“插槽”。没有它,外部传入的内容(比如 <my-card>姓名:张三</my-card>)就进不了 shadow DOM,直接被丢弃。
典型写法:
- 在 shadow 内写
<slot></slot>,它会自动透传 light DOM 子节点 - 支持具名 slot:
<slot name="header"></slot>+<my-card><h2 slot="header">标题</h2></my-card> - 别在
constructor或connectedCallback里手动复制this.innerHTML到 shadow —— 它不响应后续更新,且破坏 slot 语义
真正难的不是写出来,而是记住哪些事只能在 constructor 做、哪些必须等 connectedCallback、哪些根本不能做。一个组件跑起来容易,但要可靠响应属性变化、支持无障碍、不泄漏样式,每一步都有隐含约束。



















