iframe是唯一原生支持JS与CSS双重隔离的方案,天然隔绝执行环境、样式作用域、DOM树及localStorage,但需通过postMessage通信,存在性能开销大、弹窗裁剪、SEO差等硬性代价。

直接注入 HTML 片段会导致全局样式污染
把一段含 <style> 的 HTML 字符串直接塞进 innerHTML(比如 el.innerHTML = '<div><style>.btn{color:red}</style><button class="btn">OK</button></div>'),<style> 会逃逸到全局作用域,污染整个页面。这不是“没隔离”,是根本没建立隔离边界。
常见错误现象包括:
- 子应用的
normalize.css重置了主应用body字体大小 - 两个子应用都定义
.modal-overlay,后加载的覆盖 z-index,遮罩失效 - 第三方库(如 Ant Design)的全局样式穿透进来,按钮圆角/边框全乱
真正起效的前提只有一个:必须在 constructor() 中调用 this.attachShadow({ mode: 'closed' })。延迟到 connectedCallback 或更晚,DOM 已渲染、样式已计算,污染不可逆。
Shadow DOM 下不能用 innerHTML 注入带 style/script 的 HTML
shadowRoot.innerHTML = htmlString 是高危操作:内联 <style> 会漏出到全局,<script> 在全局上下文执行,既破坏样式隔离又引入 XSS 风险。
立即学习“前端免费学习笔记(深入)”;
正确做法是分治处理:
- 用
DOMParser解析 HTML 字符串,提取所有<style>内容 - 将每个
<style>转为CSSStyleSheet实例,再通过shadowRoot.adoptedStyleSheets = [sheet]注入 - 拦截
<script src="...">,fetch后在沙箱中执行(重绑定this/window/document到当前 shadow 上下文) - 剥离内联
<script>...</script>,禁止直接执行
adoptedStyleSheets 有局限:不支持 @import、相对路径资源(如 url(./icon.png)),这些需额外做路径重写或预加载。
data-* 属性不是隔离开关,只是选择器前缀载体
手动加 data-subapp="a" 到容器上,本身不产生任何隔离效果。它只是给 CSS 选择器提供一个可写的前缀锚点,真正起效必须配合构建时或运行时的样式重写逻辑。
典型误区:
- 只写
div[data-subapp="a"] .btn { color: red },但子应用里第三方组件(如.ant-btn)没加前缀,照样被全局规则命中 - 父应用写了
[data-theme="dark"] .btn,子应用没限定作用域,照样穿透 - 用
customElements.define()注册标签但没调attachShadow(),data-*和普通div没区别,所有样式照常级联
qiankun 的 strictStyleIsolation: true 或 MicroApp 的 <micro-app name="xxx"> 才是真正在运行时自动包裹并重写样式;你手写的 data-* 若没接入这套链路,基本无效。
iframe 是唯一原生 JS + 样式双重隔离方案,但代价明确
如果子应用必须完全独立(比如含大量第三方脚本、全局事件监听、document.write),iframe 是目前唯一浏览器原生支持的完整隔离手段。它天然隔绝 JS 执行环境、CSS 作用域、DOM 树、localStorage 等全部上下文。
但它的问题也很硬:
- 跨
iframe通信需走postMessage,无法直接调用函数或共享对象引用 - 弹窗、Tooltip、日期选择器等需脱离文档流的组件,必须挂载到
document.body,否则被裁剪 - 性能开销大:每个 iframe 都有独立渲染进程和 JS 引擎实例,内存占用翻倍
- SEO 友好性差,搜索引擎难以索引 iframe 内容
所以工程实践中,iframe 更适合作为兜底方案——比如嵌入不可控的遗留系统,而非日常子应用开发首选。



















