TA的每日心情 | 无聊 10 小时前 |
---|
签到天数: 1052 天 连续签到: 2 天 [LV.10]测试总司令
|
一、DOM简介
1.DOM构造和布局
浏览器在解析HTML文档时,会将每个标签元素抽象成DOM(Document Object Model,文档对象模型)的节点,按照标签元素层次分明的结构,将HTML文档构建成一棵DOM树,如图 1所示。
图 1 DOM树示例
浏览器按从上到下,从左到右的顺序,读取DOM树的文档节点,顺序存放到文档流。如果读取的节点是另一节点的子节点,将其按顺序存放在父节点的内部,且嵌套层级没有数量限制。
2.DOM操作
DOM定义了所有HTML元素的对象和属性,以及访问方法。通过DOM提供的方法,所有HTML元素(DOM树节点)均可被修改、创建或删除。图 2展示了一些常用的DOM操作方法。
图 2 常用的DOM操作方法
二、虚拟DOM简介
1.虚拟DOM概念
虚拟DOM(Virtual DOM)是在真实DOM树的基础上建立了一个抽象层,通过一个JavaScript对象表示真实DOM树结构,包括DOM树节点的标签名、属性、事件监听及子元素等。因此,虚拟DOM本质上是一个JS对象,如图3.b为图3.a中DOM树所对应的虚拟DOM对象。
图 3.a DOM树示例
图 3.b 虚拟DOM示例
2.React创建虚拟DOM
React.createElement(tag, attrs, ...children)方法图 4所示,第一参数是标签名,第二参数是属性对象,后面的参数是0到多个子节点。该方法负责根据源代码提供的组件属性信息生成图 3.b所示的虚拟DOM,然后通过ReactDOM.render()方法将创建的虚拟DOM转化为图 3.a所示的真实DOM。
图 4 react创建虚拟DOM对象示例
3.JSX语法创建虚拟DOM
React使用JSX语法编写虚拟DOM对象,经过babel编译后会解析生成图 4所示的目标代码,其中包括组件的type(标签名)、key(主键)、ref以及props等属性,其中props包含className、style和children等信息,然后通过ReactDOM.render()方法将虚拟DOM映射到真实DOM上。
图 5 JSX语法创建虚拟DOM示例
4.虚拟DOM的意义
React引入轻量级的虚拟DOM而不是直接对真实DOM进行操作,目的是为了实现对DOM树节点数据和状态所做的任何改动,都会被自动且高效的同步到虚拟DOM,通过与变更前的DOM树进行比较,统计出所有的差异,统一更新一次真实DOM树。
React虚拟DOM在组件状态发生变化时,构造新的虚拟DOM树并依赖算法计算出与上一个虚拟DOM树的差异,只针对变化的部分执行原生DOM操作。相较于原生DOM每次数据发生改变就去创建一个真实DOM,经过比较去修改真实DOM的操作,React虚拟DOM将多次操作合并为一次操作,提高了渲染效率。为了计算出虚拟DOM差异,React Diff算法被提出。
三、React Diff算法
React Diff算法可以计算出虚拟DOM中真正发生变化的部分,并只针对该部分进行实际DOM操作,而非重新渲染整个页面,从而保证了每次更新后页面的高效渲染。
传统diff算法通过循环递归对DOM节点进行依次对比,效率低下,算法复杂度达到O(n3),其中n是节点的总数。为了提升性能,依据三个前提策略:
(1)Web UI中DOM节点跨层级的移动操作特别少;
(2)拥有相同类的两个组件将会生成相似的树形结构;
(3)对于同一层级的一组子节点,可以通过唯一key进行区分。
react从tree diff、component diff、element diff三个层面对diff算法进行优化,保证了整体界面构建的性能。
1.Tree Diff
基于前提策略1,DOM节点跨层级的移动操作特别少,React对DOM树进行分层比较,两棵树只会对同一层次的节点进行比较,即同一父节点下的所有子节点,如图 6所示只会对相同颜色框内的DOM节点进行比较。如果发现节点不存在,那么这个节点以及它的子节点就会被完全删除,不会进行下一步的比较。这样只需要一次遍历,就可以完成整个DOM树的比较。
图 6 分层比较示例
对于DOM节点跨层级的移动操作,只有创建和删除。如图 7所示,A节点及所有子节点跨层级移动到B节点下,由于tree diff只会比较同层级节点,当发现A节点消失了,则会执行删除A节点的操作;而在B中发现多了一个子节点A,则以A为根节点的整个树作为子节点被重新创建。该操作明显影响了React性能,因此React官方建议不要进行DOM节点跨层级的操作,保持稳定的DOM结构有助于性能的提升。如若要实现跨层级效果,可以通过CSS隐藏或显示节点,而不是真的移除或添加DOM节点。
图 7 跨层级的移动操作示例
2.Component Diff
React基于组件构建应用,如果节点是组件,先看组件类型:
(1)如果是同一类型组件,按照原策略继续比较虚拟DOM树;
(2)如果不是同一类型组件,则替换该组件及其所有子节点。
如图 8所示。当组件B改变为组件E时,一旦React判断出B和E是不同类型的组件,就不会继续比较二者的结构,而是直接删除组件B,重新创建组件E。
图 8 组件比较示例
3.Element Diff
当节点处于同一层级时,react diff提供三种节点层级操作:
(1)插入:新的组件类型不在原集合中,需要对新节点执行插入操作。
(2)移动:新的组件在原集合中,且element可更新使用(如修改位置),需要进行移动操作。
(3)删除:原集合中的组件在新集合中不存在,或者存在但element发生改变,无法更新使用,则需要进行删除操作。
如图 9所示,显而易见只对DOM树进行删除了节点A的操作,但根据上述节点层级操作中原理,其过程为:原集合中包含两个span节点,新集合中包含一个span节点,此时新旧集合进行差异比较,发现原集合中A节点的属性发生改变,B节点被删除,因此在新的虚拟DOM树中执行操作为:删除A节点->删除B节点->创建新的A插入。
图 9 节点层级比较示例
React发现由于位置发生改变导致出现多余的删除和创建操作,提供了一种简单的优化策略,即允许对同一层级的子节点添加唯一key进行区分。如图 10所示,对新旧集合进行差异比较,通过key值发现仅原集合中A节点在新集合中不存在,则只需要进行删除A节点的操作即可。
图 10 具有唯一key值的节点层级比较
值得注意的是,如果使用index作为key值,当删除其中一个节点时,那么数组的后一项会向前移动,此时数据与key值无法建立唯一的关联关系,从而失去唯一key值的意义。
|
|