Shadow DOM 必须创建影子边界并注入样式才能实现样式隔离,attachShadow({ mode: 'open' }) 是推荐起点,mode: 'closed' 仅限强审计场景;样式须用 document.createElement('style') 动态注入,禁用字符串拼接;:host 和 ::slotted() 是唯一合法跨边界样式出口,继承性属性默认透入但布局属性不透。

Shadow DOM 不是“加个属性就能隔离”,而是必须创建影子边界、把样式写进这个边界里,否则外部 CSS 仍会穿透,!important 也压不住——浏览器根本不会把那条规则纳入该 shadow tree 的样式计算流程。
attachShadow({ mode: 'open' }) 是默认且推荐的起点
别用 mode: 'closed':它会让 element.shadowRoot 返回 null,DevTools 看不到结构,JS 查不到内部节点,调试时只能靠猜。只有极少数强审计场景(比如银行级 UI 基建)才需 closed,且必须配套暴露明确 API。
-
mode: 'open'下,element.shadowRoot可直接访问,支持querySelector、动态注入样式、自动化测试 - 不要在同一个元素上多次调用
attachShadow(),否则抛错Failed to execute 'attachShadow' on 'Element': Shadow root has already been attached - 若需复用逻辑,建议在
constructor中创建 shadow root,在connectedCallback中填充内容
样式必须注入 shadowRoot,不能靠字符串拼接 <style></style>
把 <style>... 直接塞进 shadowRoot.innerHTML = '...' 是常见错误——<style> 标签会被当普通 HTML 解析,不进入 CSSOM,后续 JS 修改规则(如 sheet.cssRules[0].style.color = 'red')完全无效,还可能触发 FOUC。
- 正确做法:用
document.createElement('style')创建节点,设textContent,再shadowRoot.appendChild(style) - 需要动态更新?用
CSSStyleSheet实例 +adoptedStyleSheets:Chrome/Firefox 支持replace(),Safari 17.4+ 才稳定支持 - 禁用全局
<link rel="stylesheet">:它始终加载到全局作用域,哪怕放在shadowRoot.innerHTML里也没用
:host 和 ::slotted() 是唯一合法的跨边界样式出口
想让外部主题或用户内容影响组件?只能靠这两个规范定义的伪类。其它写法(如 /deep/、::ng-deep、>>>)已被 Chrome 120+、Firefox 115+ 移除,继续用等于放弃兼容性。
立即学习“前端免费学习笔记(深入)”;
-
:host只匹配宿主元素自身(如<my-button>),适合设display、margin、[disabled]状态 -
:host-context(.dark-theme)检测祖先链(如<body class="dark-theme"><my-button></my-button></body>),不是查宿主自身 class -
::slotted(p)只作用于传入<slot>的顶层<p>,仅允许继承性属性(color、font-family、line-height),不可设margin或display
结构保护靠边界,不是靠隐藏
Shadow DOM 天然阻断 document.querySelector('.inner-btn') 这类外部查询,但 JS 执行环境仍是共享的:window、document、事件冒泡都照常穿透。所谓“私有”只存在于样式计算和 DOM 查询层面,不等于 JavaScript 隔离。
真正容易被忽略的是:所有继承性属性(font-family、color)默认透入,但布局类属性(margin、padding、display)不会——如果你发现文字没继承字体,大概率是忘了在 :host 里写 font-family: inherit。



















