closed模式仅使element.shadowRoot返回null,不提供真正防篡改能力;浏览器开发者工具仍可查看结构,::part()、事件冒泡、DOM快照等均不受限,实际隔离需配合composed: false、:host样式控制、exportparts和HTML净化。

closed 模式不能构建“不可被篡改”的组件——它只让 element.shadowRoot 返回 null,但所有常见绕过手段(DOM 快照、CSS 注入、事件监听、样式穿透)依然有效。
为什么 closed 模式不等于防篡改
浏览器开发者工具仍可展开并查看完整的 Shadow DOM 结构(Chrome/Firefox 均支持),这意味着任何有调试权限的人或自动化脚本都能读取内部节点树。更关键的是:
-
closed不阻止document.querySelectorAll对宿主元素的遍历,只要知道组件结构,就能靠el.querySelector('slot')或el.getElementsByClassName猜路径 - 外部 CSS 仍可通过
::part()(配合exportparts)或::slotted()影响内容,只要组件暴露了对应接口 - 事件冒泡不受
mode影响,el.addEventListener('click', ...)依然能捕获内部按钮触发的事件 - DOM 快照(如第三方监控 SDK 调用
el.cloneNode(true))会完整复制 shadow tree,closed完全不干预
closed 下连 querySelector 都会报错,怎么安全访问内部节点
你不能在外部写 el.shadowRoot.querySelector('input')——这会直接抛出 TypeError: Cannot read property 'querySelector' of null。正确做法是:所有内部操作必须封装在组件自身逻辑中,通过公开 API 暴露有限能力:
- 在自定义元素类里用
this.shadowRoot.querySelector(仅限构造函数或生命周期回调中,且确保this.shadowRoot存在) - 对外只提供方法如
focusInput()、setValue(val),内部再操作真实节点 - 避免在
connectedCallback中反复创建<style>或监听器,否则内存泄漏风险高
Jest + jsdom 根本不支持 closed,测试怎么办
jsdom(Jest 默认环境)完全不实现 Shadow DOM,closed 模式下连模拟访问都做不到。实际项目中必须退回到 open 模式做单元测试,否则测试代码无法 setup / teardown。这意味着:
- 上线前切
closed的做法不可靠——开发和测试跑在不同模式下,行为可能不一致 - 如果真要审计级隔离,得配套提供明确的 API 接口契约,而不是依赖
mode字段 - CI 流程里若用 Puppeteer 或 Playwright 做 E2E,倒是能真实跑
closed,但成本高、速度慢
真正影响隔离强度的不是 mode,而是这四件事
closed 是最表层的防护,实际隔离效果取决于组合策略:
-
composed: false在dispatchEvent时禁用事件重定向,防止外部监听内部事件 -
:host和:host-context()控制宿主样式边界,避免外部 class 泄漏进来 -
<slot name="xxx">+exportparts="xxx"精确开放必要插槽,而非暴露整个影子树 - HTML 输入必须严格净化(如用
DOMPurify.sanitize()),否则<script>或内联onerror仍可执行
closed 模式唯一确定生效的行为,就是让 shadowRoot 属性返回 null。其余所有“隔离”效果,都依赖你是否主动用对了 :host、exportparts、composed 和输入净化——这些才是真正容易被忽略、又无法靠 mode 自动解决的部分。

















