Shadow DOM 是唯一能够实现真正样式隔离的机制,因为 scoped 和 BEM 都无法阻止外部强制覆盖。开发者必须在构造函数或 connectedCallback 中使用 { mode: 'open' } 来创建 Shadow DOM,样式必须通过 DOM 方法注入,并且只有 :host 和 ::slotted() 这两个伪类能够跨越边界透传样式。

为什么直接写 或 BEM 类名无法真正隔离样式
许多开发者认为加上 scoped 属性或者采用 BEM 命名就能彻底解决样式冲突,但实际情况是:scoped 在 Chrome 120+ 和 Firefox 115+ 中已被移除,它从未被纳入 CSSOM 的正式规范。至于 BEM,它仅仅是一种命名约定,外部任何一条 button { color: red !important } 规则,都能轻易覆盖你精心编写的 .my-btn__text 样式。Shadow DOM 真正的强大之处在于:浏览器在解析阶段就直接切断了样式作用域——外部规则无法进入 shadowRoot 的 CSSOM,这并非权重问题,而是“根本不可见”。这才是彻底的样式隔离。
attachShadow() 必须在构造函数或 connectedCallback 中调用
这个调用时机至关重要——如果你将其放在 click 事件或 setTimeout 中再执行,浏览器会直接拒绝。而且同一元素只能 attach 一次,重复调用会抛出 Failed to execute 'attachShadow' on 'Element': Shadow root cannot be created on a node already hosting a shadow root. 错误,这点千万注意。
- 必须使用
{ mode: 'open' },'closed'模式会使element.shadowRoot始终返回null,调试时无法查看内部结构。 - 不能在
disconnectedCallback或事件处理器中调用,时机不对会直接报错。 - 如果希望复用基础样式(例如字体重置),优先考虑
CSSStyleSheet实例搭配adoptedStyleSheets,不过 Safari 16.4+ 和 Chrome 73+ 才稳定支持,需注意兼容性。
样式必须通过 DOM 方法注入,禁止拼接 innerHTML
不少开发者为了省事会直接写 shadowRoot.innerHTML = ``,看起来简洁,但问题在于: 被当作普通 HTML 解析,并未进入 CSSOM,后续用 JS 修改 style.textContent 完全无效,甚至可能引发 FOUC(样式闪烁)。
- 正确做法:使用
document.createElement('style')+textContent+appendChild(),确保样式正确进入 CSSOM。 - 必须显式继承:在
中写上:host { font-family: inherit; color: inherit; },否则设置的字体无法透传到组件内部。 ::slotted(p)只影响 slot 中的直接子,且仅支持可继承属性(color、font-family、background-color),不能设置margin或display,牢记这一点。
:host 和 ::slotted() 是仅有的合法跨边界通道
W3C 规范只保留了这两个伪类作为边界协议接口,其他如 /deep/、::ng-deep 等,在 Chrome 120+ 和 Firefox 115+ 中已被移除。这不是选择器增强,而是封装契约——用错了就等于主动放弃隔离。
:host([disabled])有效,:host-context(.theme-dark)才能响应外部。- 禁止写
:host div.title——:host只匹配宿主元素自身(比如),不匹配其内部节点。 - 禁止写
::slotted(*)—— 行为不一致,且违背了 slot 的设计初衷:内容布局责任应由使用者承担,而非组件内部控制。
真正具有挑战性的并非创建 shadow root,而是理解哪些样式需要显式继承、哪些透传必须走标准伪类、哪些 DOM 操作必须绕过字符串拼接直触 CSSOM。这些要点一旦遗漏,隔离就形同虚设。
