Shadow DOM 是 DOM 层级访问控制机制,非单纯样式隔离:closed 模式禁用调试与外部访问;slot 是显式内容分发契约,未声明则子节点滞留 Light DOM;继承属性和 CSS 变量可穿透边界;事件冒泡会重定向 target,需用 composedPath() 获取原始节点。
Shadow DOM 不是“加个样式隔离层”就完事的封装工具,它是一套 DOM 层级的访问控制机制——用错 mode、漏掉 slot 分发、或误判继承穿透,都会让“受保护”的结构瞬间裸奔。
attachShadow({ mode: 'closed' }) 实际上很难调试选 'closed' 模式后,宿主元素的 shadowRoot 属性返回 null,连 console.dir(host) 都看不到内部结构。DevTools 里即使启用了“Show user agent shadow DOM”,也对自定义 'closed' 根无效。
host.shadowRoot.querySelector() 做运行时检查或自动化测试'open'
slot 不是“插槽”,而是内容分发的显式契约没声明 <slot>,宿主元素的子节点不会自动进入 Shadow Tree —— 它们会留在 Light DOM 中,只是被视觉隐藏(display: none 或不渲染),但依然存在于主 DOM 树里,能被 document.querySelectorAll() 捕获。
<slot name="header"> 只匹配 <div slot="header">,不匹配 <h1> 或无 slot 属性的节点<slot> 是兜底入口,但仅接收“未被其他具名 slot 消费”的子节点<slot>,这些节点就变成“孤儿”,既不渲染也不报错,容易误以为内容丢失Shadow DOM 隔离的是选择器作用域和 DOM 查询,不是所有样式继承都断开。像 color、font-family、direction 这类可继承属性,以及通过 :host 显式设置的 CSS 自定义属性(--my-color),会从宿主元素向下透传进 Shadow Root。
立即学习“前端免费学习笔记(深入)”;
:host { --primary: #007bff; } → Shadow 内部可用 color: var(--primary);
body { font-size: 14px; } 不会直接影响 Shadow 内 p,除非该 p 没设 font-size 且父级又没中断继承链:host 或 :host * 上显式重置,比如 font-size: inherit; 或 all: initial;(慎用)event.composedPath())Shadow 内部触发的 click、input 等原生事件,默认会冒泡穿出 Boundary,但到达 Light DOM 时,event.target 已被重写为宿主元素,原始触发节点不可见 —— 这是封装设计,不是 bug。
event.target === someInternalButton,要用 event.composedPath()[1] 或 event.path?.[1] 查原始节点(兼容性注意)composed: true 的 CustomEvent,并附带必要 payloadstopPropagation() 在 Shadow 内部调用,只阻止在 Shadow Tree 内的冒泡,不影响穿出;要完全拦截,得在宿主上用 event.stopImmediatePropagation()
真正难的不是挂载 Shadow Root,而是判断哪些逻辑必须收进 Shadow 内、哪些必须暴露给宿主、以及在哪一层切断继承或重定向事件——这些边界一旦划错,隔离就变成黑盒陷阱。