虚拟 DOM (VirtualDOM)的本质是什么?Diff 算法是如何工作的?为什么需要 key ?
直接操作真实 DOM 的开销是昂贵的,因为它会触发浏览器复杂的渲染流水线,包括重排(Reflow)和重绘(Repaint)。为了最大限度地减少对真实 DOM 的操作,React 引入了虚拟 DOM (Virtua DOM)的概念。
虎拟 DOM 的本质是一个轻量级的 JavaScript 对象,它是对真实 DOM 结构的一层抽象和描述。当组件状态发生变化时,React 并不直接修改真实 DOM,而是遵循一个“三步走”策略:
- 1.在内存中根据新的状态,生成一棵新的虚拟 DOM 树。
- 2.通过 Diff 算法,比较新旧两棵虚拟 DOM 树的差异。
- 3.将计算出的最小化差异(patches),以批处理的方式,一次性地应用到真实 DOM 上。
React 的 Diff 算法为了实现高效对比,做出了几个关键的权衡和假设。它只进行同层级比较,并且只有当节点类型相同时才会继续深入比较。而对于同层级的一组子节点(例如列表),key 属性扮演了至关重要的角色。
key 是节点的唯一标识符。如果没有 key,当列表发生变化(如在开头插入一个元素)时,React 只能按位置逐一对比,这会错误地导致后续所有节点都被认为是“更新”了,从而进行大量不必要的 DOM 操作。而有了稳定且唯-的 key,React 就能精确地识别出哪些节点是新增的、哪些是删除的、哪些只是移动了位置。这使得 React 可以最大限度地复用已有的 DOM 元素,极大地提升了列表更新的性能。因此,key 的正确使用是 React 性能优化的一个核心实践。
Virtual DOM 的引入带来了以下好处
- 提高性能:
Virtual DOM 通过将真实 DOM 的操作转换为对虚拟 DOM 的操作,然后批量更新到真实 DOM 中,减少了对真实 DOM 的频繁操作。这样可以提高页面渲染的性能,减少了浏览器重绘和重排的次数,提升了用户体验。
- 减少 DOM 操作的次数:虚拟 DOM 可以将多次操作合并为一次操作,比如添加 1000 个节点,却是一个接一个操作的;
- 减少 DOM 操作的范围:虚拟 DOM 借助
DOM diff可以把多余的操作省掉,比如添加 1000 个节点,其实只有 10 个是新增的。
简化开发:
使用Virtual DOM可以使开发人员更专注于应用程序的逻辑和数据,而不必过多地关注 DOM 操作的细节。通过使用组件化开发模式,开发人员可以将 UI 拆分成小而独立的组件,每个组件都有自己的状态和行为,这样可以更容易地管理和维护应用程序的代码。跨平台兼容性:
由于 Virtual DOM 是一个与平台无关的抽象层,因此可以轻松地将 React 应用程序移植到不同的平台上,包括 Web、移动端和桌面端等。这样可以提高应用程序的可移植性和跨平台兼容性。方便的测试:
使用 Virtual DOM 可以方便地进行单元测试和集成测试,因为可以在内存中操作虚拟 DOM,而无需真实的浏览器环境。这样可以加快测试速度,提高开发效率。
虚拟 DOM 长什么样子?
react:
1 | const vNode={ |
vue:
1 | const vNode={ |
实现虚拟 DOM 例子
- 定义虚拟 DOM 元素
- 创建文本节点
- 将虚拟 DOM 渲染为真实 DOM
1 | // 定义虚拟 DOM 元素 |
虚拟 DOM 的缺点
需要额外的创造函数,如 createElement 或 h,但可以通过 JSX 来简化 XML 写法,严重依赖打包工具
diff 算法
虚拟 DOM 树比较:在进行更新时,React 会将当前的虚拟 DOM 树与新的虚拟 DOM 树进行比较,找出差异。
差异检测:React 使用 深度优先搜索算法(DFS)对比两棵虚拟 DOM 树,找出两棵树之间的差异。它会逐个比较节点及其子节点,找出哪些节点需要更新、添加或删除。
Diff 策略:React 使用了一些优化策略来减少 DOM 操作次数:
- 同层比较:React 只会对比相同层级的节点,不会跨层级比较。
- 唯一标识:在进行节点比较时,React 会使用节点的唯一标识(通常是 key 属性)来判断节点是否相同,从而避免不必要的更新操作。
- 节点移动:React 会尽量复用已存在的节点,而不是重新创建新的节点。如果节点顺序改变,React 会尝试通过移动节点来达到更新效果,而不是直接删除和重新插入节点。
- 批量更新:React 会将多个更新操作合并成一个批量更新,然后一次性更新到真实 DOM 中,以减少页面重绘和重排的次数。
- 应用差异:一旦找到了两棵虚拟 DOM 树之间的差异,React 就会根据这些差异来进行相应的操作,更新真实 DOM。
vue 中的 diff 算法在对新旧虚拟 dom 对比时,是从节点的两侧向中间对比,如果节点的 key 值和元素类型相同,属性值不同,就认为是不同节点,会将该节点删除重建
react 中的 diff 算法在对新旧虚拟 dom 对比时,是从节点的左边向右边对比,如果节点的 key 值和元素类型相同,属性值不同,就认为是节点同类型,只修改当前节点的属性
逻辑
- Tree diff
- 将新旧两棵树逐层对比,找出哪些节点需要更新
- 如果节点是组件就看 Component diff
- 如果节点是标签就看 Element diff
- Component diff
- 如果节点是组件,就先看组件类型
- 类型不同直接替换(删除旧的)
- 类型相同则只更新属性
- 然后深入组件做 Tree diff(递归)
- Element diff
- 如果节点是原生标签,则看标签名
- 标签名不同直接替换,相同则只更新属性
- 然后进入标签后代做 Tree diff(递归)
dom diff 有什么缺点
同级比较,存在 BUG,需要加个 唯一 Key,不能用 index 做下标
Fiber
Fiber 是 React v16 中引入的一种新的协调算法,用于调度和管理 React 的渲染和更新过程。Fiber 的目标是使 React 应用程序更加流畅和响应,减少页面卡顿和掉帧的情况。
Fiber 的核心思想是将 React 的渲染过程分解为可中断的小任务,并且可以根据优先级来调度这些任务的执行顺序。这样可以使 React 在执行渲染任务时更加灵活,可以根据页面的需要来调整任务的优先级和执行顺序,从而提高用户体验。
Fiber 的引入使得 React 具备了更高的并发能力和更灵活的调度策略,使得 React 应用程序可以更好地适应不同的场景和用户需求。
综上所述,Virtual DOM 和 Fiber 是 React 内部机制中的两个关键概念,它们共同作用于 React 的渲染和更新过程中,提高了 React 应用程序的性能、效率和响应能力。Virtual DOM 优化了对真实 DOM 的操作,而 Fiber 则优化了 React 的渲染和更新调度过程。
关于 DOM 的谣言:DOM 操作慢?虚拟 DOM 快
这句话类似于:刘翔矮(对比于姚明)
DOM 操作慢是对比于 JS 原生 API,如数组操作
任何基于 DOM 的库(Vue/React)都不可能再操作 DOM 时比 DOM 快
为什么网上有这样的谣言?
因为在某些情况下,虚拟 DOM 快
规模太大的时候,比如 100000 个节点,原生 DOM 要快,因为虚拟 DOM 要做大量的计算,规模小的时候(1000 个节点),虚拟 DOM 快
在测试插入 10000 个节点的时候,vue 要比 react 快很多,vue 的速度接近原生 JS,当然这两个都没有做任何优化