乐趣区

关于前端:jsliang-求职系列-37-React-虚拟-DOM

一 目录

不折腾的前端,和咸鱼有什么区别

目录
一 目录
二 前言
三 虚构 DOM
 3.1 要点 1:浏览器渲染过程
 3.2 要点 2:DOM 操作低廉
 3.3 要点 3:Diff 算法
四 虚构 DOM 实现原理
五 虚构 DOM 和实在 DOM 比对
六 Diff 算法

二 前言

返回目录

带着问题看文章:

  • 虚构 DOM 是什么?
  • 虚构 DOM 实现原理是什么?
  • Diff 是什么?

三 虚构 DOM

返回目录

jsliang 思路:通过 3 个要点解说虚构 DOM。

  1. 形容浏览器的渲染过程
  2. 实在 DOM 操作低廉,所以须要虚构 DOM
  3. Diff 简要做了什么,key 在当中表演什么角色

3.1 要点 1:浏览器渲染过程

返回目录

  • 创立 DOM 树。用 HTML 解析器剖析 HTML 元素,创立一棵 DOM 树。
  • 创立 CSS 规定树(CSS rule tree)。用 CSS 解析器解析 CSS 文件和 inline 款式,生成页面的样式表。
  • 创立 Render 树。将 DOM 树和 CSS 规定树关联起来,构建 Render 树。
  • 布局 Layout。依据 Render 树,浏览器开始布局,为每个 Render 树上的节点确定一个在显示器上呈现的准确坐标。
  • 绘制 Painting。在 Render 树和节点显示坐标的根底上,调用每个节点的 paint 办法,将它们绘制进去。

3.2 要点 2:DOM 操作低廉

返回目录

因为在浏览器中操作 DOM 是很低廉的:

  • 用原生 JS 或者 jQuery 操作 DOM 时,浏览器会从构建 DOM 树开始从头到尾执行一遍流程。

拓展要点:回流和重绘

频繁地操作 DOM,会产生肯定的性能问题,因而咱们须要这一层形象,在 patch 过程中尽可能地一次性将差别更新到 DOM 中,这样保障了 DOM 不会呈现性能很差的状况。

然而这样并不能解决问题,所有就有了虚构 DOM。

虚构 DOM 实质就是用一个原生的 JavaScript 对象去形容一个 DOM 节点,是对实在 DOM 的一层形象。

实在 DOM 节点

<div id="container">
  <ul>
    <li></li>
  </ul>
</div>

JS 模仿虚构 DOM

const tree = Element('div', { id: 'container'}, {Element('ul', {}, [Element('li', {}, ['新节点值'])
  ]),
});

const root = tree.render();
document.querySelector('#container').appendChild(root);

能够看到虚构 DOM 对象最根本的三个属性:

  • 标签类型
  • 标签元素的属性
  • 标签元素的子节点

3.3 要点 3:Diff 算法

返回目录

两棵树齐全比照的工夫复杂度是 O(n^3),而 React 的 Diff 算法的工夫复杂度是 O(n)

要实现这么低的工夫复杂度,意味着在比拟差别时只会对同一层级的节点进行比拟,因为如果进行齐全的比拟,算法理论复杂度会过高,所以舍弃了这种齐全的比拟形式,而采纳同层比拟。

Diff 算法的外围就是对虚构 DOM 节点进行深度优先遍历,并对每一个虚构节点进行编号,在遍历的过程中对同一个层级的节点进行比拟,最终失去比拟后的差别。

假如当初的虚构 DOM 的更新前后为:

// DOM 节点值
const dom = `
  <div id="container">
    <p>123</p>
    <ul>
      <li class="jsliang1">li 节点 </li>
      <li> 旧节点值 </li>
    </ul>
  </div>
`;

// 旧节点
const tree = Element('div', { id: 'container'}, [Element('p', {}, ['123']),
  Element('ul', {}, [Element('li', { class: 'jsliang1'}, ['li 节点']),
    Element('li', {}, ['旧节点值']),
  ]),
]);

// 变成上面新的虚构 DOM
const tree = Element('div', { id: 'container'}, [Element('h3', {}, ['小标题']), // 更新节点
  Element('ul', {}, [Element('li', { class: 'jsliang2'}, ['li 节点']), // 更新属性或者属性值
    Element('li', {}, ['新节点值']), // 更新文本
  ]),
]);

Diff 获取虚构 DOM 节点变更的 4 种状况比拟:

  • 节点类型变了 <p> -> <h3>。间接 Replace,将旧节点卸载并装载新节点。
  • 节点类型一样,仅仅属性或者属性值变了 。间接 Props,更新节点。
  • 文本变了 。间接 Text,批改文字内容就行了。
  • 减少、删除或者挪动了子节点 。间接 Reorder,这个办法比较复杂,小伙伴们具体能够去理解下。

Diff 的实现,最粗犷的办法就是遍历每个新虚构 DOM 节点,和旧虚构 DOM 节点比对。在旧 DOM 中是否存在,不同就卸载原来的上新的。

这时候不得不提一下 React 或者 Vue 外面的 key,咱们在写业务的时候,被告知 key 值不能是索引值 index

为什么这样子呢?

假如咱们有 4 个元素,旧的元素是 1、2、3、4,新的元素是 1、3、2、4,然而如果咱们用了索引值 index,那么它们就始终是 0-3。

这样子的话,咱们 React 或者 Vue 就没法比拟好的监听它的一个变动。

所以一般来说,咱们将 key 值定位成数组的 id 或者其余值,不便它变动的时候去监听。

这样子通过 Diff 比拟结束之后,咱们就能够获取须要变动的内容,最终去更新实在 DOM 节点。

四 虚构 DOM 实现原理

返回目录

  • 虚构 DOM 实质上是 JavaScript 对象,是对实在 DOM 的形象
  • 状态变更时,记录新树和旧树的差别
  • 最初把差别更新到真正的 DOM 中

五 虚构 DOM 和实在 DOM 比对

返回目录

长处:

  • 保障性能上限 :虚构 DOM 能够通过 Diff 找出最小差别,而后批量进行 patch,这种操作尽管比不上手动优化,然而比起粗犷的 DOM 操作性能要好很多,因而虚构 DOM 能够保障性能上限。
  • 无需手动操作 DOM:虚构 DOM 的 Diffpatch 都是在一次更新中主动进行的,咱们无需手动操作 DOM,极大进步开发效率。
  • 跨平台 :虚构 DOM 实质上是 JavaScript 对象,而 DOM 与平台强相干,相比之下虚构 DOM 能够进行更不便地跨平台操作,例如服务器渲染、挪动端开发等。

毛病:

  • 无奈进行极致优化 :在一些性能要求极高的利用中虚构 DOM 无奈进行针对性的极致优化,例如 VS Code 采纳间接手动操作 DOM 的形式进行极其的性能优化。

六 Diff 算法

返回目录

比拟原生虚构 DOM 和新的虚构 DOM 的区别,应用 Diff(Different)算法

如上图,在 React 中,对于 setState,它采纳异步操作,对立对 state 中的数据进行更改。


首先 ,比对第一层的 DOM 节点,如果它雷同,则往下持续比照;如果它不同,则进行比照,更新第一层及以下的 DOM 节点。

而后 ,比对第二次的 DOM 节点……

最初 ,造成一种比对算法。

所以总结下来就是:

  • 把树形构造依照层级合成,只比拟同级元素。
  • 给列表构造的每个单元增加惟一的 key 属性,不便比拟。
  • React 只会匹配雷同 classcomponent(这外面的 class 指的是组件的名字)
  • 合并操作,调用 componentsetState 办法的时候, React 将其标记为 dirty。到每一个事件循环完结, React 查看所有标记 dirtycomponent 从新绘制.
  • 抉择性子树渲染。开发人员能够重写 shouldComponentUpdate 进步 Diff 的性能。

jsliang 的文档库由 梁峻荣 采纳 常识共享 署名 - 非商业性应用 - 雷同形式共享 4.0 国内 许可协定 进行许可。<br/> 基于 https://github.com/LiangJunrong/document-library 上的作品创作。<br/> 本许可协定受权之外的应用权限能够从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处取得。

退出移动版