iframe是浏览上下文级隔离,拥有独立window、document、history等;Shadow DOM是DOM子树级封装,共享顶层window,不创建新上下文。

iframe 和 Shadow DOM 的隔离层级完全不同
iframe 是浏览上下文级隔离,Shadow DOM 是 DOM 子树级封装。前者拥有独立的 window、document、CSSOM、甚至自己的 history 和 storage;后者只是同一 document 内的一棵“影子树”,共享顶层 window,不创建新浏览上下文。
这意味着:你可以在 Shadow DOM 里直接调用 fetch 或 console.log,但 iframe 里的脚本默认无法读取父页 localStorage(除非同源且显式暴露);反过来,父页想操作 iframe 里的元素,必须走 iframe.contentDocument,而操作 Shadow DOM 只需 element.shadowRoot —— 前者跨上下文,后者仍在同一 JS 执行环境。
样式和脚本的穿透规则截然相反
Shadow DOM 默认阻断外部样式进入、内部样式外溢,::slotted 和 :host 是可控出口;iframe 则完全不参与父页样式链,它的 CSSOM 彼此毫无交集,连 font-family 都不继承(除非显式设置 inherit)。
脚本方面:
- 在 Shadow DOM 中动态插入的 <script> 会执行,但受作用域限制,无法污染全局变量(除非手动挂到 window)
- iframe 里所有脚本都在独立 window 下运行,document.write、location.href = 'javascript:...' 等危险操作天然被沙箱化(尤其开了 sandbox 属性后)
自动化测试和元素定位的底层逻辑差异
Playwright/Selenium 定位 iframe 内元素,必须先切换上下文(如 page.frame({ url: /login/ }) 或 frameElement.contentFrame()),否则 locator('#btn') 永远找不到;而 Shadow DOM 元素虽然不在主 DOM 树中,但只要路径可达(比如 page.locator('my-comp').locator('button')),Playwright 会自动递进 shadowRoot 查找 —— 它把 Shadow DOM 当作“可穿透的嵌套结构”,而非隔离环境。
立即学习“前端免费学习笔记(深入)”;
常见翻车点:
- 用 document.querySelector('iframe') 拿到元素后立刻读 .contentDocument → 返回 null(没等 load 事件)
- 对 Shadow DOM 元素用 shadowRoot.querySelector('div') 却忘了检查 shadowRoot 是否为 null(mode: 'closed' 时必然为 null)
- 把 iframe 塞进 Shadow DOM 后,还指望用 ::part(my-frame) 控制其 border → 无效,因为 iframe 是替换元素,UA 样式不响应 ::part
什么时候该选哪个,不能只看“隔离”二字
要防第三方代码劫持页面?必须用 <iframe sandbox="allow-scripts">,Shadow DOM 压根不拦 document.write 或 eval。
要封装一个带样式的按钮组件,且希望复用时不污染全局?用 attachShadow({ mode: 'open' }),iframe 在这里纯属杀鸡用牛刀。
真正容易被忽略的是:两者可以共存。比如在 Shadow DOM 内部动态创建并插入一个同源 iframe,此时你既要等 iframe.onload,又要确保它已挂载到 shadowRoot(connectedCallback 里操作比 constructor 更稳妥),两层加载时机叠加,稍一疏忽就拿到空的 contentDocument。



















