前端analysis | 知其所以然

Web Components 和 Shadow DOM

2025-05-14

Web Components 和 Shadow DOM **并不使用 <iframe>**,尽管它们在某些方面(如封装性)看起来类似。让我们详细区分一下它们:


Web Components 和 Shadow DOM 的本质

  • Web Components 是一组前端 API,包括:

    • Custom Elements(自定义元素)
    • Shadow DOM(影子 DOM)
    • HTML Templates(模板)
  • Shadow DOM 提供了 封装样式和结构 的能力,使组件内部的 DOM 和样式不会影响外部,也不被外部影响。

关键点:Shadow DOM 是运行在主文档上下文中的 DOM 树,并非独立的文档环境。


iframe 的作用和区别

  • <iframe> 会嵌入一个完全独立的 浏览器上下文(包括 JS 执行环境、CSS、全局变量等)。
  • 它和主页面之间的隔离程度远高于 Shadow DOM,属于“进程级别”的隔离(安全沙箱)。
  • 适用于嵌入第三方页面、广告、或完全隔离的内容。

🔍 对比总结表

特性 Shadow DOM <iframe>
是否隔离样式 ✅ 是(封装) ✅ 是(完全隔离)
是否隔离 JS 作用域 ❌ 否(共享全局作用域) ✅ 是(独立 JS 环境)
是否是独立文档 ❌ 否 ✅ 是(有自己的 document
加载性能影响 ✅ 轻量 ❌ 较重,尤其是多个 iframe
常用于 UI 组件封装、Design Systems 广告、嵌入外部内容、跨域内容展示

✅ 举个例子:Shadow DOM

1
2
3
4
5
6
7
8
9
10
11
12
<my-component></my-component>

<script>
class MyComponent extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `<style>p { color: red; }</style><p>Hello!</p>`;
}
}
customElements.define('my-component', MyComponent);
</script>

上面这个组件中的 <p> 标签样式不会影响页面其他内容,也不会被外部样式污染 —— 但它仍运行在主页面的上下文中,不是通过 iframe 实现的。


🧠 总结一句话:

Shadow DOM 是“样式和 DOM 封装”,而 iframe 是“运行环境级别的隔离”。两者不是一回事,Shadow DOM 不使用 iframe。

✅ 一、目标:将组件资源拆分为多个文件

比如:

  • my-component.js:定义组件逻辑
  • my-component.html:定义模板结构
  • my-component.css:定义样式

✅ 二、实现方式:使用 JavaScript 动态加载外部资源

📄 my-component.html

1
2
3
<template>
<p class="greeting">Hello from Shadow DOM!</p>
</template>

📄 my-component.css

1
2
3
4
.greeting {
color: blue;
font-weight: bold;
}

📄 my-component.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}

async connectedCallback() {
const [htmlText, cssText] = await Promise.all([
fetch('my-component.html').then(res => res.text()),
fetch('my-component.css').then(res => res.text())
]);

const template = document.createElement('template');
template.innerHTML = `
<style>${cssText}</style>
${htmlText}
`;

this.shadowRoot.appendChild(template.content.cloneNode(true));
}
}

customElements.define('my-component', MyComponent);

📄 使用:

1
2
<script type="module" src="my-component.js"></script>
<my-component></my-component>

✅ 三、好处

  • 结构清晰:HTML、CSS、JS 分离,便于维护
  • 可重用性高:可作为独立模块复用
  • 保持封装性:依旧使用 Shadow DOM,样式不会外泄

⚠️ 注意事项

  1. 跨域问题:资源必须允许浏览器访问(同源或正确 CORS 设置)。
  2. 性能:每次创建组件都可能触发 fetch,可使用缓存或优化加载逻辑。
  3. 资源依赖:需要确保 CSS 和 HTML 文件在 JS 加载之前可访问。

this.attachShadow({ mode: 'open' })Promise.all()原生 Web 技术的一部分,
而 Vue、React、Angular 这类框架有自己独特的抽象和机制。


🔍 一句话总结:

原生 Web Components 用浏览器原生 API(如 Shadow DOM、Custom Elements)来封装组件;
Vue、React、Angular 则用自己设计的“组件系统”来实现相似目的,但不依赖 Shadow DOM(默认情况下)。


✅ 对比核心区别

特性/行为 原生 Web Components Vue / React / Angular
组件机制 使用 class + customElements 使用框架语法(如 Vue SFC、JSX、TSX)
封装 DOM(结构) 使用 Shadow DOM 使用虚拟 DOM(VNode),默认非封装
封装样式(CSS 隔离) 自动隔离 via Shadow DOM 使用 CSS Modules、Scoped CSS、JSS
attachShadow({mode}) 显式调用以开启封装 没有这个 API,框架自动管理
异步加载(如 Promise.all 使用浏览器 API 通常用 async/await 或框架方式(hooks、lifecycle)
运行时依赖 无(原生浏览器支持) 依赖框架运行时(Vue、React 等)

🧪 举个例子比较一下:

🌐 原生 Web Component

1
2
3
4
5
6
7
8
9
10
11
class MyComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}

connectedCallback() {
this.shadowRoot.innerHTML = `<p>Hello</p>`;
}
}
customElements.define('my-component', MyComponent);

⚛️ React 组件(等价实现)

1
2
3
function MyComponent() {
return <p>Hello</p>;
}

React 不使用 Shadow DOM,而是通过虚拟 DOM 管理结构,靠 JS 隔离样式或使用模块化 CSS。


🤔 那为什么框架不用 Shadow DOM 呢?

✅ 优点(不用的原因):

  1. 性能问题:Shadow DOM 创建成本较高,虚拟 DOM 更易批量优化。
  2. 工具生态好:框架有更强的开发体验(如 HMR、TypeScript 支持)。
  3. 灵活性高:组件样式、结构更可控,不依赖浏览器底层 API。
  4. 跨平台渲染支持更好(如 SSR、React Native)。

❗但:框架也可以使用 Shadow DOM

  • Vue 和 Angular 可以启用 shadow DOM 模式(shadow: true)。
  • React 可以封装原生 Web Components 并渲染到 Shadow Root。
    但这通常只在特定场景下使用,如构建设计系统、嵌入外部平台等。

✅ 总结

对比点 原生 Web Components Vue/React/Angular
是否使用 Shadow DOM ✅ 默认使用 ❌ 默认不使用(可选启用)
是否使用 Promise.all ✅ 直接用原生 API ✅ 也用,但通常包在生命周期钩子中
封装性 ✅ 强,原生封装 ❌ 默认弱,用工具实现封装
上手难度 🚧 较高,需手写底层逻辑 ✅ 更高层抽象,开发体验好
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏