shadow dom 的隔离是分层的:dom 查询受限、样式需手动重置、事件默认穿透、js 完全不隔离;mode 仅控制 js 访问 shadowroot,open 可调试,closed 仅阻断查询但不阻止样式继承与事件冒泡。

Shadow DOM 的 mode 参数决定隔离强度
Shadow DOM 的隔离能力不是默认“全封闭”的,关键看创建时传的 mode 值。用 mode: "open" 时,外部 JS 可以通过 element.shadowRoot 访问内部节点;而 mode: "closed" 则彻底屏蔽访问——但注意,"closed" 并不阻止样式穿透或事件冒泡,它只挡 JS 查询。
实际项目中几乎没人用 "closed",因为调试困难、框架(如 Lit、Stencil)也不依赖它。真正起作用的是组合策略:配合 :host、::slotted 和 CSS all: initial 才能逼近“代码隔离”效果。
-
mode: "open"是唯一可调试、可被现代工具链支持的选项 -
mode: "closed"无法被 DevTools 选中,也无法被querySelector定位,但事件仍会冒泡到 light DOM - 不要指望
mode单独实现 JS 作用域隔离——Shadow DOM 本身不创建新执行上下文
样式不会自动隔离,必须主动切断继承
Shadow DOM 不自动重置 CSS,父级样式(比如全局 body { font-size: 16px })会继承进来,h1、button 等原生标签照样受外部 reset.css 影响。想真正“独立”,得手动干预。
最可靠的方式是在 Shadow Root 的根元素上加 all: initial,再显式声明需要的样式。别依赖 inherit 或省略,否则字体、颜色、边距全靠猜。
- 在
:host上写all: initial; font-family: sans-serif;是起点 - 避免用
* { box-sizing: border-box }这类通配符,它在 Shadow DOM 中无效(除非显式加到:host或子元素) -
::slotted(*)只能控制 slot 内容的样式入口,不能改变其内部原有样式规则
事件冒泡照常发生,监听位置决定是否“感知”组件内部
Shadow DOM 不阻断事件流,click、input 等事件依然会从内部元素冒泡到 light DOM。所谓“隔离”,只是 DOM 查询受限,不是事件隔离。
如果你在组件外监听 click,照样能捕获内部按钮触发的事件;如果不想这样,得在 Shadow Root 边界调用 event.stopPropagation(),或者用 event.composed = false 创建非跨 Shadow 边界的事件。
- 默认所有原生事件都是
composed: true,所以会穿透 Shadow Boundary - 自定义事件要显式设
composed: false才能限制在 Shadow 内部 -
event.target在跨 Shadow 边界后会变成#shadow-root,不是原始触发元素——这是调试时最常踩的坑
JS 变量和函数仍共享全局作用域
Shadow DOM 不是沙箱,它不提供 JavaScript 执行环境隔离。你在 Shadow 内写的 const foo = 1,和外部脚本里的 foo 没有任何作用域关系——它们都在同一个 window 下。所谓“代码隔离”,本质是 DOM 结构 + 样式 + 事件流向的约束,不是 JS 模块隔离。
真要隔离逻辑,得靠模块系统(ESM)、闭包或 Custom Element 的私有字段(#privateMethod),而不是靠 Shadow Root 自身。
- Shadow Root 内的
import仍走标准模块解析,和外部模块共用同一套node_modules - 用
class定义 Custom Element 时,实例方法默认是公开的,#私有字段才真正不可外部访问 - 不要误以为把
script标签塞进template就能“隔离执行”——它只是被插入到 Shadow DOM,执行时机和作用域完全不变
Shadow DOM 的隔离是分层的:DOM 查询受限、样式需手动重置、事件默认穿透、JS 完全不隔离。把这四层各自理清楚,才不会在调试时反复怀疑“为什么我的样式又被覆盖了”或者“这个事件怎么跑到外面去了”。











