首先浏览器加载一个HTML页面时会经过创建DOM 树、创建样式规则(style rules)、构建渲染树(render tree)、布局layout 和 绘制页面(painting)。
传统的原生api或jQuery去操作DOM的时候,浏览器会从构建DOM树开始从头到尾执行一遍流程,所以频繁的操作DOM的代价是昂贵的。还会出现页面卡顿,影响用户的体验。
虚拟DOM就是为了解决这个问题而被设计出来,加入一次操作中有10次操作的动作,虚拟DOM不会立即执行这个操作,而是将这10次更新的diff内容
虚拟DOM是在DOM的基础上在内存建立了一个抽象层,对数据和状态所做的任何改动,都会被自动且高效的同步到虚拟DOM,最后再批量同步到DOM中
render执行的结果得到的并不是真正的DOM节点,而仅仅是javascript对象,称之为虚拟DOM
虚拟DOM具有批处理和高效的Diff算法,可以无需担心性能问题而随时‘刷新’整个页面,因为虚拟DOM能保证对界面上真正变化的部分进行实际的DOM操作
【传统DOM操作(eg:innerHtml)】:render html+重建所有DOM元素
【虚拟DOM】:render 虚拟DOM + diff算法+更新必要的DOM元素
Vue.js将DOM抽象成一个以JavaScript对象为节点的虚拟DOM树,以VNode节点模拟真实DOM,可以对这颗抽象树进行创建节点、删除节点以及修改节点等操作,在这过程中都不需要操作真实DOM,只需要操作JavaScript对象,大大提升了性能。修改以后经过diff算法得出一些需要修改的最小单位,再将这些小单位的视图进行更新。这样做减少了很多不需要的DOM操作,大大提高了性能。
diff的过程就是调用patch函数,就像打补丁一样修改真实dom。
function patch (oldVnode, vnode) {
if (sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode)
} else {
const oEl = oldVnode.el
let parentEle = api.parentNode(oEl)
createEle(vnode)
if (parentEle !== null) {
api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl))
api.removeChild(parentEle, oldVnode.el)
oldVnode = null
}
}
return vnode
}
patch
函数有两个参数,vnode
和oldVnode
,也就是新旧两个虚拟节点。在这之前,我们先了解完整的vnode都有什么属性,举个一个简单的例子:
// body下的 <div id="v" class="classA"><div> 对应的 oldVnode 就是
{
el: div //对真实的节点的引用,本例中就是document.querySelector('#id.classA')
tagName: 'DIV', //节点的标签
sel: 'div#v.classA' //节点的选择器
data: null, // 一个存储节点属性的对象,对应节点的el[prop]属性,例如onclick , style
children: [], //存储子节点的数组,每个子节点也是vnode结构
text: null, //如果是文本节点,对应文本节点的textContent,否则为null
}
需要注意的是,el属性引用的是此 virtual dom对应的真实dom,patch
的vnode
参数的el
最初是null,因为patch
之前它还没有对应的真实dom。