咱们知道不论是vue仍是react当中,都是利用virtual dom(下面简称vd)来表示真实的dom,由于操做真实的dom的代价是昂贵的,即便是查找dom节点的操做都是昂贵的,因此在优化的方法当中,就有缓存dom的查找结果的一个优化,那么既然真实dom的操做是昂贵的,因此若是咱们在使用diff算法来比较两个dom之间的差别的时候,就要遍历全部的dom来进行对比,若是是按照真实的dom来进行diff算法的比较的话,那么就至关消耗性能了,所以vd应运而生。那么怎么将真实的dom和vd对应起来呢?咱们知道,dom不外乎三个特性:
一、标签名
二、各类属性
三、孩子节点
所以,若是要用vd来表示dom的话咱们就能够这样定义。vue
class VNode {
constructor(tagName, attributes, children) {
this.tagName = tagName
this.attributes = attributes
this.children = children
}
}
复制代码
好比有这样的domnode
<div id="div" class="classVal">
<span>child</span>
</div>
复制代码
那么vd就是这样的react
{
tagName: 'div',
attributes: {
'id': 'div',
'class': 'classVal'
},
children: [{
tagName: 'span',
attributes: null,
children: ['child']
}]
}
复制代码
固然,这里vd的定义少了TEXT节点,因此咱们加上TEXT节点,TEXT节点直接返回里面的innerText/textContent,就像上面的children: ['child']
,咱们定义一个叫h的函数,用来建立vd,包括TEXT节点,它接受四个参数,分别以下:git
tagName: 标签名
text: 若是是TEXT节点,那么就是TEXT的内容,即innerText/textContent
attributes: dom属性的价值对
children: dom的孩子vd
复制代码
function h(tagName, text, attributes, children) {
// 判断到是TEXT节点,直接返回TEXT里面的内容
if(text) {
return text
}
return new VNode(tagName, attributes, children)
}
复制代码
好了,VD大概就是这样子表示,那么咱们若是根据vd还原成真实的dom呢,其实很简单,就是根据一一对应关系还原呗:github
function createElement(vnode) {
var el = null;
// 文本元素
if(typeof vnode === "string") {
el = document.createTextNode(vnode);
return el;
}
// 还原dom
el = document.createElement(vnode.tagName);
// 还原attribute
for(var key in attributes) {
el.setAttribute(key, attributes[key]);
}
// 还原孩子节点
var children = vnode.children.map(createElement);
children.forEach(function(child) {
el.appendChild(child);
});
return el;
}
复制代码
关于vd的理解差很少就这样,若是有须要补充的或者指正的,望不吝赐教。算法
有了vd后,咱们要怎么比较两个dom树之间的不一样呢,固然不能无脑的使用innerHTML对整块树更新(backbone就是这样),而是针对更改的地方进行更新或者替换,那么咱们就须要依赖diff来找出两棵树之间的不一样。
传统的diff算法,是须要跨级对比两个树之间的不一样,时间复杂度为O(n^3),这样的对比是没法接受的,因此react提出了一个简单粗暴的diff算法,只对比同级元素,这样算法复杂度就变成了O(n)了,虽然不能作到最优的更新,可是时间复杂度大大减小,是一种平衡的算法,下面会提到。缓存
那么怎么理解它是只对比同级和具体它是怎么对比的呢?
基于diff算法的同级对比,咱们先讲下对比的过程当中,它主要分为四种类型的对比,分别为:
一、新建create: 新的vd中有这个节点,旧的没有
二、删除remove: 新的vd中没有这个节点,旧的有
三、替换replace: 新的vd的tagName和旧的tagName不一样
四、更新update: 除了上面三点外的不一样,具体是比较attributes先,而后再比较children
写成代码就是这样:bash
diff(newVnode, oldVNode) {
if(!newVNode) {
// 新节点中没有,说明是删除旧节点的
return {
type: 'remove'
}
} else if(!oldVNode) {
// 新节点中有旧节点没有的,说明是删除
return {
type: 'create',
newVNode
}
} else if(isDiff(newVNode, oldVNode)) {
// 只要对比出两个节点的tagName不一样,说明是替换
return {
type: 'replace',
newVNode
}
} else {
// 其余状况是更新节点,要对比两个节点的attributes和孩子节点
return {
type: 'update',
attributes: diffAttributes(newVNode, oldVNode),
children: diffChildren(newVNode, oldVNode)
}
}
}
// 对比孩子节点,其实就是遍历全部的孩子节点,而后调用diff对比
function diffChildren(newVnode, oldVNode) {
var patches = []
// 这里要获取两个节点中的最大孩子数,而后再进行对比
var len = Math.max(newVnode.children.length, oldVNode.children.length);
for(let i = 0; i <len; i++) {
patches[i] = diff(newVnode.children[i], oldVnode.children[i])
}
return patches
}
// 对比attribute,只有两种状况,要不就是值改变/新建,要不就是删除值,对比dom只有setAttribute和removeAttribute就知道了
function diffAttributes(newVnode, oldVNode) {
var patches = []
// 获取新旧节点的全部attributes
var attrs = Object.assign({}, oldVNode.attributes, newVNode.attributes)
for(let key in attrs) {
let value = attrs[key]
// 只要新节点的属性值和久节点的属性值不一样,就判断为新建,不论是更新和真正的新建都是调用setAttribute来更新
if(oldVNode.attributes[key] !== value) {
patches.push({
type: 'create',
key,
value: newVnode.attributes[key]
})
} else if(!newVNode.attributes[key]) {
patches.push({
key,
type: 'remove'
})
}
}
return patches
}
// 判断两个节点是否不一样
function isDiff(newVNode, oldVNode) {
// 正常状况下,只对比tagName,可是text节点对比没有tagName,因此要考虑text节点
return (typeof newVNode === 'string' && newVNode !== oldVNode)
|| (typeof oldVNode === 'string' && newVNode !== oldVNode)
|| newVNode.tagName !== oldVNode.tagName
}
复制代码
结合代码,你们对比下面的图,图里面remove没有列出来,remove和create差很少缘由,相信你们知道什么状况下是remove。 mvc
完整的代码你们能够看下个人git地址:github.com/VikiLee/XLM…app
咱们更新dom的时候,尽可能不要整棵树进行更新,须要作到细颗粒的更新,要作到细颗粒地更新就必须知道两棵树直接的不一样,因此须要使用diff算法来进行对比,可是传统的diff算法虽然能作到细颗粒准确地更新,可是它须要花销大量的时间来进行比对,因此有来react的改版的diff算法,只比较同一级的元素,这样能够作到快速的比对,为O(n),即便这样,在对比两棵树的时候,咱们仍是须要遍历全部的节点,咱们知道dom的操做是昂贵的,即便是查找,也是昂贵的一个过程,特别是在节点不少的donm树下,因此虚拟dom应运而生,虚拟dom避开了直接操做dom的缺点,而是直接对比内存中vd,使得对比速度进一步获得质地提高。