Shadow DOM是唯一真正生效的样式隔离机制,未调用attachShadow()则无隔离;外部样式因浏览器在计算阶段跳过跨boundary匹配而完全无效,:host和::slotted()是W3C唯二合法穿墙方式。
不启用 attachShadow(),所谓“封装”只是类名前缀或 scoped 属性的自我安慰。浏览器在样式计算阶段就跳过跨 boundary 匹配,外部 button { color: red !important } 对 Shadow 内按钮完全无效——不是权重不够,是根本看不见。
常见错误现象包括:document.querySelector('.shadow-inner-btn') 查不到节点、font-family 未继承、动态改 style.textContent 无反应。这些都不是写法问题,而是没走对路径。
attachShadow({ mode: 'open' }) 是调试友好但生产风险高;mode: 'closed' 才是微前端等场景的强制要求(element.shadowRoot 返回 null 是设计使然,不是 bug)<style scoped> 已被 Chrome 120+、Firefox 115+ 彻底移除,BEM 类名无法阻止全局规则穿透attachShadow(),否则抛 InvalidStateError
写 shadowRoot.innerHTML = '<style>.btn { color: blue }</style>' 看似省事,实则让 <style> 被当作文本节点解析,不进入 CSSOM,后续 JS 修改完全失效。
正确做法是创建真实 <style> 元素并 append 到 shadowRoot,或使用现代 API:
立即学习“前端免费学习笔记(深入)”;
document.createElement('style') + shadowRoot.appendChild(style)
new CSSStyleSheet() 实例,再赋值给 shadowRoot.adoptedStyleSheets = [sheet](Chrome 73+/Safari 16.4+ 支持)<link rel="stylesheet"> 直接塞进 innerHTML——它会加载到主文档 <head>,彻底失去隔离W3C 规范只保留这两个伪类作为跨 boundary 接口。滥用 ::slotted(*) 或试图用 :host div 选内部子元素,等于放弃封装意义。
关键限制必须清楚:
:host([disabled]) 只响应宿主元素自身属性变更;:host-context(.dark) 才能响应祖先 <body class="dark">
::slotted(p) 只作用于被 <slot> 投影进来的顶层 <p>,且仅支持可继承属性(color、font、background-color),禁设 margin、display
:host { font-family: inherit; },否则默认回退 Times New Roman直接 shadowRoot.innerHTML = htmlString 是危险操作:<style> 逃逸到全局、<script> 在 window 上执行、<link> 加载进主文档——样式污染和 XSS 风险同时发生。
安全链路是:
new DOMParser().parseFromString(html, 'text/html') 解析字符串doc.body.childNodes,对每个 <style> 提取内容转 CSSStyleSheet 后注入 adoptedStyleSheets
<script src="...">,fetch 后在沙箱中重绑定 this/window/document 执行<script>,避免意外执行最容易被忽略的是:所有样式卸载必须靠 DOM 移除整块子树,而不是手动删 <style> 标签——浏览器重建 CSSOM 才算真正清理干净。