Virtual DOM 的实现原理
Virtual DOM:虚拟DOM。
学习目标:
- 了解什么是虚拟DOM,Vue和React内部为什么使用虚拟DOM(虚拟DOM的作用)
- Vue内部的虚拟DOM是基于Snabbdom库改造的。
- Snabbdom的基本使用
- Snabbdom的源码解析
- 通过源码解析更好的了解虚拟DOM的工作原理
什么是Virtual DOM
Virtual DOM(虚拟DOM),是由普通的 JS 对象来描述DOM对象,因为不是真实的DOM对象,所以叫 Virtual DOM。
真实DOM和虚拟DOM对比
真实DOM:
通过打印一个真实DOM的成员(div的属性)发现,一个DOM对象(div)的成员非常多,所以创建一个DOM对象的成本是非常高的。
PS: DOM对象的成员继承自原型链(HTML元素接口:div继承自HTMLDivElement),不能用Object.keys获取到。
let element = document.createElement('div')
// DOM对象的成员继承自原型链(div继承自HTMLDivElement),不能用Object.keys获取到
// console.log(Object.keys(element))
let s = ''
for (var key in element) {
s += key + ','
}
console.log(s)
虚拟DOM:
使用Virtual DOM来描述真实DOM:
{
sel: 'div', // 标签
data: {},
children: undefined,
text: 'Hello Virtual DOM', // 文本内容
elm: undefined,
key: undefined
}
可以发现创建一个虚拟DOM,它的成员是非常少的。
也就是创建一个虚拟DOM的成本要小于创建一个真实DOM。
为什么使用 Virtual DOM
- 手动操作 DOM 比较麻烦,还需要考虑浏览器兼容性问题。
- 虽然有 jQuery 等库简化 DOM 操作并解决了兼容性问题。
- 但是随着项目的复杂,DOM操作复杂提升,既要考虑操作数据,又要考虑操作 DOM。
- 为了简化 DOM 的复杂操作,于是出现了各种 MVVM框架,MVVM 框架解决了视图和状态的同步问题,也就是:
- 当数据发生变化,自动更新视图
- 当视图发生变化,自动更新数据
- 过去,为了简化视图的操作,可以使用模板引擎。
- 但是模板引擎没有解决跟踪状态变化的问题,即当数据发生变化的时候,无法获取上一次的状态。只好把页面中的元素删除,然后重新创建。无法最小范围更新视图。
- 于是 Virtual DOM 出现了。
- Virtual DOM 的好处是,当状态改变时不需要立即更新 DOM,只需要创建一个虚拟 DOM 树来描述 DOM,Virtual DOM 内部将弄清楚如何有效(diff)的更新 DOM。
- 内部使用 diff 算法,找到状态的差异,只更新变化的部分。
Github 上 virtual-dom 描述
虚拟DOM是一个 JavaScript DOM模型,支持create Element元素创建、diff差异计算和patch补丁操作(将差异更新到视图),以实现高效的重新渲染。
动机
手动操作比较麻烦,并且很难跟踪以前的DOM状态。
解决此问题的方法是编写代码,就像在状态变化时重新创建整个DOM一样。
当然,如果您每次更改应用程序状态时,都重新创建整个DOM,那您的应用程序将非常缓慢,并且输入字段将失去焦点。
virtual-dom 是modules模块的集合,旨在提供声明性的方式来表示应用程序的DOM。
因此,无需在应用程序状态更改时更新 DOM,只需创建一个用于描述您想要的DOM状态的虚拟树或VTree。
然后 virtual-dom 将在不重新创建所有 DOM 节点的情况下,找出如何更有效的将DOM更新为您描述的状态。
virtual-dom 实现在状态发生变化时更新视图的方式是:
创建完整的VTree的视图,高效的修补dom,使其更新为您描述的状态。
这样可以避免在代码中跟踪之前的状态以及手动操作DOM,从而为web应用提供了清晰且可维护的呈现逻辑。
总结:
- 虚拟 DOM 可以维护程序的状态,跟踪上一次的状态
- 通过比较前后两次状态的差异更新真实 DOM
虚拟 DOM 的作用及虚拟 DOM 库
虚拟 DOM 的作用
- 维护视图和状态的关系
- 虚拟DOM可以记录上一次数据的变化,只更新状态变化的部分
- 复杂视图情况下提升渲染性能
- 比较数据变化差异,只更新差异部分的场景,使用虚拟DOM性能更优
- 除了渲染成 真实DOM 以外,还可以渲染成其他平台使用的内容:
- 实现SSR(服务端渲染),把虚拟DOM转换成普通的HTML字符串,常用框架:
- Nuxt.js - 基于vue的服务端渲染框架
- Next.js - 基于React的服务端渲染框架
- 原生应用(Weex/React Native)
- 小程序(mpvue/uni-app)
- 等
- 实现SSR(服务端渲染),把虚拟DOM转换成普通的HTML字符串,常用框架:
性能对比
任何情况下,直接操作真实DOM,性能肯定高于使用虚拟DOM渲染这个完整的DOM,因为虚拟DOM还要进行转化为真实DOM的操作。
上面说的虚拟DOM性能更优,指的是在某些场景下的某些操作。
虚拟DOM的性能优势在于对DOM的子孙节点进行增删改操作。
操作类型 | 真实DOM | 虚拟DOM |
---|---|---|
增加节点 | 获取父节点,添加子节点 | 减少逻辑代码:不需要再编写获取父节点的代码,直接在新的虚拟DOM中添加子节点,自动同步 |
删除节点 | 获取父节点,找到子节点,执行删除 | 减少逻辑代码:原理同上 |
修改子节点属性 | 获取子节点,找到子节点,修改属性 | 减少逻辑代码:原理同上 |
在不同层级添加修改节点 | 方式1:编写大量定位代码,和操作DOM的代码 | 减少逻辑代码:原理同上 |
在不同层级添加修改节点 | 方式2:新建一个DOM替换,其中包含一些重复的内容,消耗资源 | 最小范围操作DOM:不需要新建,只需对比新旧虚拟DOM,只更新差异的地方 |
对列表排序 | 创建新的列表替换 | 最小范围操作DOM:原理同上 |
- 减少逻辑代码
- 虽然在某些时候,虚拟DOM损失了一些性能
- 但是它减少了操作DOM的逻辑代码,是实现数据驱动的重要部分。
- 最小范围操作DOM:最小范围更新,只更新差异。
- 它相比于新建一个DOM,成本小很多:
- 复杂DOM更新时,有很多内容没有变化,不需要重新渲染。
- 减少重新渲染的未变化部分,也就是减少重绘的可能性,降低浏览器消耗。
- 它相比于新建一个DOM,成本小很多:
Virtual DOM 开源库
- Snabbdom
- Vue 2.x 内部使用的 Virtual DOM 就是改造的 Snabbdom
- 源代码只有大约200 SLOC(Single Line Of Code)(行)
- 所以学习虚拟DOM,研究Snabbdom的源代码比直接查看Vue的源代码轻松的多
- 代码量少,但是可以通过模块扩展功能
- Snabbdom中的模块类似插件的机制
- 源码基于TypeScript开发
- 官宣:自己是最快的 Virtual DOM 之一
- virtual-dom - 最早的虚拟DOM的开源库