Shadow DOM 和 Virtual DOM ?别再傻傻分不清

第一次听到 Shadow DOM 和 Virtual DOM 的时候,我也懵了。

两个都叫 DOM,长得还挺像,是不是有什么关系?是不是一个是另一个的升级版?

答案是:它们除了名字里都有 "DOM",几乎没有任何关系。

一个是浏览器原生能力,一个是框架层面的性能优化手段。今天咱们就把这俩彻底掰扯清楚,以后面试被问到也能对答如流。


一、先看个段子热热身

面试官:说说你对 Shadow DOM 和 Virtual DOM 的理解。

我:Shadow DOM 是影子,Virtual DOM 是虚拟的,加起来就是"虚拟的影子"。

面试官:???你可以回去了。

玩笑归玩笑,下面进入正题。


二、Virtual DOM(虚拟 DOM)

2.1 它是什么

Virtual DOM 本质上就是用 JavaScript 对象来描述真实 DOM 结构。

比如这段 HTML:

html

xml 复制代码
<div id="app" class="container">
  <h1>Hello</h1>
  <p>World</p>
</div>

用 Virtual DOM 表示大概是这样的:

js

css 复制代码
{
  tag: 'div',
  props: { id: 'app', class: 'container' },
  children: [
    { tag: 'h1', children: 'Hello' },
    { tag: 'p', children: 'World' }
  ]
}

它就是一个普普通通的 JS 对象,存在内存里,跟浏览器没半毛钱关系。

2.2 为什么需要它

直接操作真实 DOM 很慢吗?其实单次操作并不慢 ,慢的是频繁操作引发的重排(reflow)和重绘(repaint) 。

早期 jQuery 时代,我们这样写:

js

javascript 复制代码
$('#app').html('<h1>Hello</h1>');
$('#app').append('<p>World</p>');
$('#list').empty();

每次操作都直接怼到真实 DOM 上,浏览器就得重新计算布局。数据一变就来一轮,性能直接裂开。

Virtual DOM 的思路是:

  1. 数据变化时,先在内存里生成一棵新的 Virtual DOM 树
  2. 和旧的树做 diff 对比,找出真正变化的部分
  3. 只把这些变化批量、最小化地更新到真实 DOM

用一句话概括:用 JS 的计算成本,换 DOM 的更新成本。

2.3 它解决了什么

  • ✅ 减少不必要的 DOM 操作
  • ✅ 让开发者可以"声明式"写 UI,不用手动操作 DOM
  • ✅ 跨平台能力(React Native、小程序等都受益于此)

2.4 它不解决什么

  • ❌ 它不解决样式隔离问题
  • ❌ 它不解决DOM 结构封装问题
  • ❌ 它只是框架内部的优化手段,不是浏览器标准

三、Shadow DOM(影子 DOM)

3.1 它是什么

Shadow DOM 是 Web Components 标准 的一部分,是浏览器原生支持的能力。

它的核心作用是:把一棵 DOM 子树封装起来,让它和外部的 DOM 互相隔离。

听起来有点抽象,上代码:

html

xml 复制代码
<div id="host"></div>

<script>
  const host = document.getElementById('host');
  const shadowRoot = host.attachShadow({ mode: 'open' });
  
  shadowRoot.innerHTML = `
    <style>
      p { color: red; font-size: 20px; }
    </style>
    <p>我是影子 DOM 里的段落</p>
  `;
</script>

外部写:

css

css 复制代码
p { color: blue; }

影子 DOM 里的 p 依然是红色,外部样式进不去,内部样式也出不来。

这就是样式隔离 + DOM 隔离。

3.2 它的关键特性

特性 说明
样式隔离 外部 CSS 选择器无法穿透影子边界
DOM 封装 document.querySelector 找不到影子内部节点
事件重定向 内部事件冒泡到宿主元素时,target 会被重定向
原生支持 不需要任何框架,浏览器自带

3.3 它解决了什么

  • ✅ 组件样式污染问题(想想那些 .btn 冲突的血案)
  • ✅ 组件的真正封装(内部结构外部拿不到)
  • ✅ 微前端场景下的样式隔离
  • ✅ <video>、<input> 这些原生控件内部其实就用了 Shadow DOM

3.4 它不解决什么

  • ❌ 不解决性能问题
  • ❌ 不解决数据驱动视图的问题
  • ❌ 兼容性相对复杂(虽然现在主流浏览器都支持了)

四、核心对比:一张表说清楚

维度 Virtual DOM Shadow DOM
本质 JS 对象树 真实的 DOM 子树
归属 框架实现(React/Vue) 浏览器原生标准
目的 性能优化 + 声明式 UI 封装 + 样式隔离
是否真实存在 否,只在内存里 是,真实 DOM
能否操作样式 不涉及 核心能力
能否提高性能 核心目标 不涉及
跨平台 是 否,仅浏览器
出现时间 2010 年左右(React 带火) 2011 年左右(Web Components)

一句话总结:

Virtual DOM 解决的是"怎么更新得更快 ",Shadow DOM 解决的是"怎么封装得更好"。它俩根本不在一个赛道上。


五、常见的三个误区

误区一:Shadow DOM 是 Virtual DOM 的升级版 ❌

完全不是。一个是框架层,一个是浏览器层,没有谁替代谁的关系。

误区二:用了 Shadow DOM 性能会更好 ❌

Shadow DOM 主要解决封装问题,性能提升不是它的目标。某些场景下反而因为有边界的存在,样式计算会更慢一点。

误区三:Virtual DOM 比直接操作 DOM 快 ❌

这是被传烂了的说法。Virtual DOM 本身有 diff 开销,它不一定比手动优化过的 DOM 操作快 。它的价值在于:让你在不用手动优化的前提下,也能获得不错的性能。

尤雨溪本人也说过类似的话:Virtual DOM 的性价比在于"可维护性 + 性能"的平衡。


六、它们能一起用吗?

当然可以,而且很常见。

比如你用 Vue 或 React 开发,同时引入了一个基于 Web Components 的 UI 库(比如某些设计系统组件),这时候:

  • 框架内部用 Virtual DOM 做 diff 更新
  • 自定义元素内部用 Shadow DOM 做样式隔离

两者井水不犯河水,各干各的活。

举个 React 的例子:

jsx

javascript 复制代码
function App() {
  return (
    <div>
      <h1>React 渲染的内容</h1>
      <my-web-component />  {/* 内部使用 Shadow DOM */}
    </div>
  );
}

React 负责 Virtual DOM 那层,my-web-component 内部自己玩 Shadow DOM。


七、总结

再用一张图帮大家记忆:

text

markdown 复制代码
┌─────────────────────────────────────┐
│          浏览器真实 DOM              │
│  ┌──────────────┐  ┌─────────────┐  │
│  │ 普通 DOM 树  │  │ Shadow DOM  │  │
│  │              │  │  (封装)    │  │
│  └──────────────┘  └─────────────┘  │
└─────────────────────────────────────┘
              ▲
              │ 批量更新
              │
┌─────────────────────────────────────┐
│      Virtual DOM(内存中的 JS 对象)  │
│         React / Vue 在维护           │
└─────────────────────────────────────┘

记住两句话就够了:

  1. Virtual DOM 是"内存里的 DOM 草稿",用来算最小更新量。
  2. Shadow DOM 是"DOM 里的保险箱",用来做样式和结构的封装。

下次再有人问你这两个的区别,直接把这篇文章甩过去 😎


如果这篇文章对你有帮助,点个赞再走呗~ 有问题欢迎评论区交流!

相关推荐
风花一世月3 小时前
🏮 我把三坊七巷做成了能飞的 3D 地图(901 个真实建筑轮廓、20 处景点,在线直接玩)
前端
特立独行的猫A3 小时前
用仓颉语言写一个串口调试助手:cj-tauri 实战
前端
郑州光合科技余经理3 小时前
本地生活平台搭建:跨业态用户标识怎么贯通
java·开发语言·前端·后端·uni-app·php·ai编程
数据掘金3 小时前
埋点事件命名升级时老数据怎么办
前端
对空六课3 小时前
Vue 组件曝光埋点为什么会重复触发?去重方案与实现思路
服务器·前端·算法·数据分析
isha3 小时前
Univer拆解——从类与方法的视角分析
前端·javascript
羲云3 小时前
不想再让用户手写 Markdown:零依赖的所见即所得编辑器最新版发布 V0.2.0
前端
deli0073 小时前
限流算法到底怎么选?令牌桶 vs 漏桶 vs 滑动窗口 3 种同屏实测
前端
陈前端3 小时前
AI 猜错了接口返回格式之后,我开始怀疑喂给它的上下文
前端