经常看到,常用的框架react、vue设计原理是用到了virtual dom及diff算法,那么究竟什么是virtual dom?virtual dom有什么优势和缺点?如何实现?diff算法的核心又是什么?
1. virtual dom的概念
virtual dom是对dom的抽象,其实就是一个JavaScript对象,这个对象是更加轻量级的对dom的描述,用于提高性能。为了方便,后面virtual dom简写为vdom。
1.1 🌰
假设真实的dom对象如下:
1234<ul id='list'><li class='item'>itemA</li><li class='item'>itemB</li></ul>那么对应的vdom则为
123456789101112131415161718192021{tag:'ul', // 元素的标签类型attrs:{ // 表示指定元素身上的属性id:'list'},children:[ // ul元素的子节点{tag: 'li',attrs:{className:'item'},children:['itemA']},{ tag: 'li',attrs:{className:'item'},children:['itemB']}]}
vdom参数主要包括tag,attrs,children
- tag: 指定元素的标签类型,如:’ul’;
- attrs: 指定元素身上的属性,如id,class,style,自定义属性等;
- children: 表示指定元素是否有子节点,参数以数组的形式传入,如果是文本就是数组中为字符串。
2. virtual dom的优缺点
2.1 vdom优点
- 我们知道在前端性能优化的一个手段就是尽可能少地操作dom,因为操作dom相对较慢,频繁变动dom会造成浏览器的回流或者重绘,使性能不好。所以我们抽象dom,在操作dom的步骤尽可能一次性并且较少改动的完成,这样保证了dom不会出现性能很差的情况;
- 实现了函数式UI编程;
- 更好的跨平台,比如Node.js的SSR(服务端渲染),ReactNative,React VR、weex都是使用了vdom,相关的内容可以查阅一下。
2.2 vdom缺点
- 首次大量dom操作的情况下,性能不如直接操作原生dom,单一的、频繁的更新dom,vdom会花费更多的时间处理计算,比较差异的工作。
3. virtual dom核心
这里借助snabbdom.js这个库来理解vdom的核心,因为snabbdom源码简短,我们常用的vue框架的虚拟dom实现也是参考了snabbdom.js的实现。其结构如下:
|
|
3.1 snabbdom的核心
- compile,通过h函数把真实dom编译成vnode虚拟节点对象;
- diff,通过算法,记录oldVnode和newVnode之间有什么变化;(核心)
- patch, 如果把这些变化用更新到真实dom上去。
3.2 将vnode转化为真实dom
上面说到,h函数是把真实的dom节点编译成vnode对象,vnode对象其实就是单个vdom的创建,其代码如下(代码建议结合snabbdom.js源码查看):
12345678 export function vnode (sel: string | undefined,data: any | undefined,children: Array<VNode | string> | undefined,text: string | undefined,elm: Element | Text | undefined): VNode {const key = data === undefined ? undefined : data.key;return { sel, data, children, text, elm, key };}
其返回的对象即为单个vdom;我们知道dom其实是一个tree,可以有节点和属性等;snabbdom利用h函数通过递归调用来创建dom tree,代码如下:
vdom是dom对象的抽象,所以需要将vdom与真实的dom对应起来,在snabbdom是通过createElm函数实现的
可以看到,通过createElm函数,可以生成对应的dom,把data里定义的各种属性设置到dom上,也就是把vdom与真实的dom对应起来了。