You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
tag 存放 DOM 元素的标签名
props 存放标签内的所有属性
children 存放标签内嵌子元素
由于 DOM 本身就以树形结构展示,所以使用 JavaScript 对象类型就能很简单的表示。用 JavaScript 对象来表示 DOM 的好处有两点:1. 提升性能;2. 跨平台;具体优势参照如下。
虚拟 DOM 带来的优势:
提升性能:原生 DOM 因为浏览器厂商需要实现众多的规范(各种 HTML5 属性、DOM事件),即使创建一个空的 div 也要付出昂贵的代价(JS 操作真实 DOM 元素会带来巨大的性能消耗),而虚拟 DOM 则可通过 diff 算法比对新旧 Virtual DOM Tree (JavaScript 原生对象),找到需要变更的 DOM 节点,然后仅在真实 DOM 上对改动节点及其子节点进行更新操作,而不是更新整个视图,从而减少 JavaScript 操作真实 DOM 的带来的性能消耗。
跨平台:抽象渲染过程,实现跨平台的能力。(真实 DOM 局限于浏览器,而表示为 JavaScript 对象,就可以应用于安卓 / IOS等不同平台,甚至是小程序)
很多人认为虚拟 DOM 最大的优势是 diff 算法,减少 JavaScript 操作真实 DOM 的带来的性能消耗。虽然这一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种GUI。
-- 摘自 虚拟 DOM 到底是什么?
HTML 代码 -> Virtual DOM
h 函数是实现 HTML 代码向 Virtual DOM 转换的关键。主流的虚拟 DOM 库(snabbdom、virtual-dom),通常都有一个 h 函数。React 是通过 babel 将 jsx 转换为 h 函数渲染的形式,而 Vue 是使用 vue-loader 将模版转为 h 函数渲染的形式(也可以通过 babel-plugin-transform-vue-jsx 插件在 vue 中使用 jsx,本质还是转换为 h 函数渲染形式)。
关于子节点的对比,可以说是 diff 算法中,变动最多的部分,因为前面的部分,各个库对比的方向基本一致,而关于子节点的对比,各个仓库都在前者基础上不断得进行改进。
为什么需要改进子节点的对比方式? 如果我们直接按照深度优先遍历的方式,一个个去对比子节点,子节点的顺序发生改变,那么就会导致 diff 算法认为所有子节点都需要进行 replace,重新将所有子节点的虚拟 DOM 转换成真实 DOM,这种操作是十分消耗性能的。但是,如果我们能够找到新旧虚拟 DOM 对应的位置,然后进行移动,那么就能够尽量减少 DOM 的操作。
参考:
什么是 Virtual DOM ?
与其将 “Virtual DOM” 视为一种技术,不如说它是一种模式,是一种编程概念。在这个概念里, UI 以一种理想化的,或者说“虚拟的”表现形式被保存于内存中,并通过如 ReactDOM 等类库使之与“真实的” DOM 同步。
概念
Virtual DOM 本质就是一个普通的 JavaScript 对象,包含了 tag、props、children 三个属性:
由于 DOM 本身就以树形结构展示,所以使用 JavaScript 对象类型就能很简单的表示。用 JavaScript 对象来表示 DOM 的好处有两点:1. 提升性能;2. 跨平台;具体优势参照如下。
虚拟 DOM 带来的优势:
HTML 代码 -> Virtual DOM
h 函数是实现 HTML 代码向 Virtual DOM 转换的关键。主流的虚拟 DOM 库(snabbdom、virtual-dom),通常都有一个 h 函数。React 是通过 babel 将 jsx 转换为 h 函数渲染的形式,而 Vue 是使用 vue-loader 将模版转为 h 函数渲染的形式(也可以通过 babel-plugin-transform-vue-jsx 插件在 vue 中使用 jsx,本质还是转换为 h 函数渲染形式)。
h 函数
h 函数接受三个参数,对应位置分别为 DOM 元素的标签名 tag、属性 props、子节点 children,最终返回一个虚拟 DOM 的对象。以 React JSX 为例,babel 会将 jsx 转换为以 h 函数实现的形式,最终返回 Virtual DOM 对象:
渲染 Virtual DOM
前面提到,虚拟 DOM 一大优势在于它可以跨平台,这也就意味着,不同平台上,渲染虚拟 DOM 的方式也有所不同。以浏览器为例,虚拟 DOM 会被渲染为真实 DOM,然后被浏览器解析。
以浏览器环境为例,渲染 Virtual DOM 的流程如下:
将虚拟 DOM 渲染成真实 DOM 后,只需要插入到对应的根节点即可:
JSX -> 虚拟DOM -> 真实DOM
diff 算法
本质:对比新旧 Virtual DOM 对象的差异,将改动的部分更新到视图上。
代码实现:实现一个 diff 函数,接收新旧 Virtual DOM 作为参数,并将改动部分以某种方式渲染到视图上。
虚拟 DOM 库对 diff 算法的实现
不同虚拟 DOM 库对于 diff 算法有着自己不同的实现:
最开始出现的是 virtual-dom 这个库,通过深度优先搜索与 in-order tree 来实现高效的 diff 。
然后是 cito.js 采用两端同时进行比较的算法,将 diff 速度拉高到几个层次。
紧随其后的是 kivi.js,在 cito.js 的基础上提出两项优化方案,使用 key 实现移动追踪以及基于 key 的最长自增子序列算法应用(算法复杂度 为O(n^2))。但这样的 diff 算法太过复杂了,于是后来者 snabbdom 将 kivi.js 进行简化,去掉编辑长度矩离算法,调整两端比较算法。速度略有损失,但可读性大大提高。
再之后,就是著名的 vue2.0 把 sanbbdom 整个库整合掉了。
VDOM 对比规则
2.1 对比新旧节点的属性
2.2 对比新旧节点的子节点差异,通过 key 值进行重排序,key 值相同节点继续向下遍历
子节点对比
关于子节点的对比,可以说是 diff 算法中,变动最多的部分,因为前面的部分,各个库对比的方向基本一致,而关于子节点的对比,各个仓库都在前者基础上不断得进行改进。
为什么需要改进子节点的对比方式? 如果我们直接按照深度优先遍历的方式,一个个去对比子节点,子节点的顺序发生改变,那么就会导致 diff 算法认为所有子节点都需要进行 replace,重新将所有子节点的虚拟 DOM 转换成真实 DOM,这种操作是十分消耗性能的。但是,如果我们能够找到新旧虚拟 DOM 对应的位置,然后进行移动,那么就能够尽量减少 DOM 的操作。
virtual-dom 对子节点添加 key 值,其中 key 在当前子节点集合中必须是唯一标识的,通过 key 值的对比,来判断子节点是否进行了移动。通过 key 值对比子节点是否移动的模式,被各个库沿用,这也就是为什么主流的视图库中,子节点如果缺失 key 值,会有 warning 的原因。
cito.js 再此基础上改动了对子节点对比的算法,引入了两端对比,将 diff 算法的速度提升了几个量级。
kivi 的 diff 算法在 cito 的基础上,引入了最长增长子序列,通过子序列找到最小的 DOM 操作数。
为什么不用 index 作为 key ?
参考:轻松理解为什么不用Index作为key
具体例子参考文章,总结如下:
数组遍历时产生的 index 满足子节点 key 唯一标识的要求,当遍历的数组不发生更改时(增 / 删),看不出 index 作为 key 所带来的问题。一旦数组发生了更改,数组遍历时就会重新对新的子节点赋予 index,新 VDOM 中的子节点对应的 key 被重新赋值,导致
新旧 VDOM 中相同 key 的子节点对应的值不同。致命的问题在于,若数组改动发生在第一个子节点,无论是删除还是增加,都会让 diff 算法将后续值不相同的一连串子节点识别为需要重新渲染的目标,增大了开销。
The text was updated successfully, but these errors were encountered: