React essence note - 9 - 真正理解虚拟 DOM:React 选它, 真的是为了性能吗?

真正理解虚拟 DOM:React 选它, 真的是为了性能吗?

什么是虚拟DOM? - WHAT

  • 虚拟DOM 本质上是 JS和DOM之间的一个映射缓存,它在形态上表现为一个能够描述DOM结构及其属性信息的JS对象
  • 虚拟DOM是JS对象
  • 虚拟DOM是对真实DOM的描述

虚拟DOM的工作流程 - HOW

  • 挂载阶段: React将结合 JSX 的描述, 构建出虚拟DOM树, 然后通过ReactDOM.render实现虚拟DOM真实DOM的映射(触发渲染流水线)
  • 更新阶段: 页面的变化在作用于真实DOM之前,会先作用于虚拟DOM, 虚拟DOM将在JS层借助算法先对比出具体有哪些真实DOM需要被改变,然后将这些改变作用于真实DOM

以往的DOM操作解决方法

    1. 原生JS支配下的 人肉DOM时期.
    1. 解放生产力的先导阶段: jQuery时期.
    1. 民智初启: 早期模板引擎方案 => 由于模板引擎更倾向于点对点解决繁琐的DOM操作的问题,它在能力和定位上既不能够,也不打算替换掉jQuery,两者共存
const staff = [{name:'leslie1',age:12},{name:'leslie2',age:13},{name:'leslie3',age:14}]
<table>
  {% staff.forEach(function(person){ %}
  <tr>
    <td>{% student.name %}</td>
    <td>{% student.age %}</td>
  </tr>
  {% }); %}
</table>
<!-- 模板语法就是把JS和HTML结合在一起的一种规则.而模板引擎做的事情也非常容易理解:
  把 staff 这个数据源读进去, 塞到预置好的 HTML 模板里, 然后把两者融合在一起, 吐出一段目标字符串给你. 这段字符串的内容, 其实就是一份标准的、可用于渲染的 HTML 代码, 它将对应一个 DOM 元素. 最后, 将这个 DOM 元素挂载到页面中去, 整个模板的渲染流程也就走完了
 -->
 <script>
  // 数据和模板融合出 HTML 代码
  var targetDOM = template({data:staff})
  // 添加到页面中
  document.body.appendChild(targetDOM)
 </script>
  • 模板引擎的的工作流程
      1. 读取HTML模板并分析它,分离出其中的JS信息
      1. 将分析出的内容拼接成字符串, 动态生成JS代码
      1. 运行动态生成的JS代码, 吐出 "目标HTML"
      1. 将 "目标HTML" 赋值给 innerHTML,触发渲染流水线,完成真实 DOM 的渲染
  • 目标引擎的缺点: 1:不能指望它去做太复杂的事情; 2: 它在性能上的表现并不尽如人意: 由于不够智能,它更新 DOM 的方式是将已经渲染出 DOM 整体注销后再整体重渲染,并且不存在更新缓冲这一说. 在 DOM 操作频繁的场景下,模板引擎可能会直接导致页面卡死.

Virtual DOM 的出现

  • 从模板引擎的设计思想上得到了启发, 明确要走数据驱动试图这条基本道路: 模板引擎的数据驱动视图方案,核心问题在于对真实 DOM 的修改过于大刀阔斧, 既然操作真实 DOM 对性能损耗这么大,那我操作假的 DOM 不就行了?

Virtual DOM是如何解决问题的

  • 模板引擎的工作方式:

  • VirtualDOM的工作方式:

  • 这里模板加了引号,是以为虚拟DOM在是线上并不总事借助模板(React使用JSX,JSX本质不是模板,而是一种JS语法糖 React.createElement)
  • 区别就在于多出了一层虚拟DOM作为缓冲层,这个缓冲层带来的好处是: 当DOM操作(渲染更新)比较频繁时,它会将前后两次的虚拟DOM树进行对比,定位出需要更新的部分, 生成一个补丁集,最后把补丁打在需要更新的那部分真实DOM上,实现精确的差量更新

  • 图中的diffpatch都是函数名, 这些函数取材于一个独立的虚拟 DOM 库. 之所以写明了具体流程对应的函数名,是因为我发现面试的时候,很多面试官习惯于用函数名指代过程,但不少人不清楚这个对应关系(尤其是 patch),会非常影响作答
  • VirtualDOMReduxt 一样都是独立的, 不依附于任何具体的框架.

React 选用虚拟 DOM, 真的是为了更好的性能吗?

  • 在整个DOM操作的演化过程中,主要矛盾并不在于性能,而在于开发者写的爽不爽, 在于研发体验和研发效率. 虚拟DOM不是别的, 正是前端开发们为了追求更好的研发体验和研发效率而创造出来的高阶产物.
  • 虚拟 DOM 并不一定会带来更好的性能,React 官方也从来没有把虚拟 DOM 作为性能层面的卖点对外输出过.虚拟 DOM 的优越之处在于,它能够在提供更爽、更高效的研发模式(也就是函数式的 UI 编程方式)的同时,仍然保持一个还不错的性能.

模板渲染 VS VirtualDOM

  • 性能问题属于前端领域复杂度比较高的问题. 当我们量化性能的时候, 往往并不能只追求一个单一的数据, 而是需要结合具体的参照物、渲染的阶段、数据的吞吐量等各种要素来作分情况的讨论
  • 对比一下

  • 简单结论
# 从图中可以看出, 模板渲染的步骤1, 和虚拟 DOM 渲染的步骤1、2都属于 JS 范畴的行为, 这两者是具备可比性的, 我们放在一起来看:动态生成 HTML 字符串的过程本质是对字符串的拼接, 对性能的消耗是有限的;而虚拟 DOM 的构建和 diff 过程逻辑则相对复杂, 它不可避免地涉及递归、遍历等耗时操作. 因此在 JS 行为这个层面, 模板渲染胜出. 

# 模板渲染的步骤3, 和虚拟 DOM 的步骤3 都属于 DOM 范畴的行为, 两者具备可比性, 因此我们仍然可以愉快地对比下去:模板渲染是全量更新, 而虚拟 DOM 是差量更新. 

# 乍一看好像差量更新一定比全量更新高效, 但你需要考虑这样一种情况:数据内容变化非常大(或者说整个发生了改变), 促使差量更新计算出来的结果和全量更新极为接近(或者说完全一样). 

# 在这种情况下, DOM 更新的工作量基本一致, 而虚拟 DOM 却伴随着开销更大的 JS 计算, 此时会出现的一种现象就是模板渲染和虚拟 DOM 在整体性能上难分伯仲:若两者最终计算出的 DOM 更新内容完全一致, 那么虚拟 DOM 大概率不敌模板渲染;但只要两者在最终 DOM 操作量上拉开那么一点点的差距, 虚拟 DOM 就将具备战胜模板渲染的底气. 

因为虚拟 DOM 的劣势主要在于 JS 计算的耗时, 而 DOM 操作的能耗和 JS 计算的能耗根本不在一个量级, 极少量的 DOM 操作耗费的性能足以支撑大量的 JS 计算

# 当然, 上面讨论的这种情况相对来说比较极端. 在实际的开发中, 更加高频的场景是这样的:我每次 setState 的时候只修改少量的数据, 比如一个对象中的某几个属性, 再比如一个数组中的某几个元素. 在这样的场景下, 模板渲染和虚拟 DOM 之间 DOM 操作量级的差距就完全拉开了, 虚拟 DOM 将在性能上具备绝对的优势.

虚拟DOM的价值不在性能,而在别处!

虚拟DOM的优势在哪呢?

  • 这个问题需要从 虚拟DOM解决了哪些关键问题来分析
    1. 研发体验/研发效率的问题: 这一点前面已经反复强调过, DOM 操作模式的每一次革新, 背后都是前端对效率和体验的进一步追求.虚拟 DOM 的出现, 为数据驱动视图这一思想提供了高度可用的载体, 使得前端开发能够基于函数式 UI 的编程方式实现高效的声明式编程. (函数式UI: 比如JSX)
    1. 跨平台的问题: 虚拟DOM是对真实渲染内容的一层抽象.若没有这种抽象, 视图层和渲染平台紧密耦合在一起, 为了描述同样的视图内容,你可能要分别在 Web 端和 Native 端写完全不同的两套甚至多套代码. 但现在中间多了一层描述性的虚拟 DOM,它描述的东西可以是真实 DOM,也可以是iOS 界面、安卓界面、小程序......同一套虚拟 DOM,可以对接不同平台的渲染逻辑,从而实现一次编码,多端运行, 其实说到底,跨平台也是研发提效的一种手段,它在思想上和1是高度呼应的.

    1. 批量更新: 除了差量更新以外, 批量更新也是虚拟 DOM 在性能方面所做的一个重要努力: 批量更新 在通用虚拟 DOM 库里是由 batch 函数来处理的. 在差量更新速度非常快的情况下(比如极短的时间里多次操作同一个 DOM), 用户实际上只能看到最后一次更新的效果. 这种场景下, 前面几次的更新动作虽然意义不大, 但都会触发重渲染流程, 带来大量不必要的高耗能操作. ==> batch 的作用是缓冲每次生成的补丁集,它会把收集到的多个补丁集暂存到队列中,再将最终的结果交给渲染函数,最终实现集中化的 DOM 批量更新
posted @ 2020-11-09 23:03  荣光无限  阅读(97)  评论(0)    收藏  举报