虚拟DOM
在此章节,讲解一下关于Vue虚拟DOM的知识点。
那什么是虚拟DOM呢,这里了解一下页面渲染的过程。
页面渲染
当我们请求页面时,HTTP请求返回就会返回页面HTML数据:
1、首先会将HTML信息构建成DOM节点树
2、之后将CSS样式构建成CSSOM树
3、之后将上面的两棵树结合形成一个render树
4、Layout,根据render树计算出每一个渲染对象的位置和尺寸,将其安置在浏览器窗口的正确位置
5、最后根据排版后的结构进行页面的绘制Painting
注意,这是一个极其复杂耗时的过程,假如我们执行操作,一次改一点,一次改一点,那么就需要一遍又一遍的删除原来的DOM,然后执行上面的页面渲染重新构建DOM,页面就会卡死,所以Vue的虚拟DOM就是帮助解决这个问题的。
虚拟DOM
Vue的虚拟DOM就是解决上面页面反复加载问题的,Vue会构建了和真实DOM结构相似的一个虚拟的DOM,注意:真实DOM是HTML构建的,但是,虚拟DOM只是构建一个对象而已。
当页面发生变化了,虚拟DOM会帮助生成一个新的节点,然后比较一下新旧两个节点(注意,这两个所谓的节点,都只是虚拟DOM弄的对象而已),如果新旧节点不一样,再把新节点构建成真实的DOM节点放到真实DOM上,这一顿操作下来,就相当于打补丁,但是真的缓解了很多,就不需要直接摧毁原来的DOM重建。
更新过程
1、创建虚拟DOM的JS对象
2、Diff算法进行节点的新旧比较
3、patch打补丁,更新到真实DOM上
首先我们先把这个虚拟更新的函数写出来,不需要全看懂,下面会介绍:
function patch (oldVnode, vnode) {
// 当oldVnode不存在时
if (!oldVnode)) {
// 创建新的节点
createElm(vnode)
} else {
// 判断新老节点是否是同一节点
// 如果是同一节点,执行patchVnode进行子节点比较;
// 如果不是同一节点,新节点直接替换老节点;
if (sameVnode(oldVnode, vnode)) {
// 对oldVnode和vnode进行diff,并对oldVnode打patch
patchVnode(oldVnode, vnode) // diff在这里
}else {
createElem(vnode) // 根据Vnode生成新元素, 新节点直接替换老节点
}
}
}
1、创建虚拟DOM
所谓创建虚拟DOM,便是将真实的DOM节点以JS对象的形式表达出来,举个🌰:
<div id="app" class="page-box">
<p>this is demo</p>
{
tag: 'div'
data: {
id: 'app',
class: 'page-box'
},
children: [
{
tag: 'p',
text: 'this is demo'
}
]
}
2、Diff算法
创建完虚拟DOM后,当数据发生更新,就需要判断是不是需要进行真实DOM的替换,这时候,虚拟DOM就会生成一个新的虚拟节点对象vnode,它有一个对象的旧的虚拟节点对象oldvnode,(这里oldvnode和vnode就对应上面patch函数的两个参数)下面就是要判断这两个节点是不是发生改变了。
旧节点不存在
首先如果压根旧节点不存在,需要重新生成一个节点,那么什么判断都不需要做,直接生成就好了:
if(!oldvnode){
createElement(vnode);
}
判断两个节点基本属性是否一致
如果旧节点存在,那么就需要判断两个节点的基本属性,假如两个节点,一个是div,另外一个是span,那压根就不用比了,直接生成新节点替换就成了。
说说比较重要的属性:
tag 属性即这个vnode的标签属性:div、span、p等等
data 属性包含了最后渲染成真实dom节点后,节点上的class,attribute,style以及绑定的事件
children 属性是vnode的子节点
text 属性是文本属性
elm 属性为这个vnode对应的真实dom节点
key 属性是vnode的标记,这个key在diff算法中非常重要
所以在进行Diff算法前,需要判断一下这些重要属性是不是一样的。
function sameVnode (a, b) {
return (
a.key === b.key &&
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
)
}
Diff算法真实应用
了解一下Diff的核心:
1. 两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构
2. 同一层级的一组节点,他们可以通过唯一的id进行区分
简单来讲,就是Vue会按照真实DOM生成一个虚拟DOM,之后当某节点数据发生改变后,虚拟DOM就会生成一个新的节点,然后自己的新节点和旧节点比较一下,如果发生改变了,那么就会直接在真实DOM上进行替换,俗称打补丁。
到了patchVnode方法,说明新老节点为同一节点,让我们来看看patchVnode函数,其实diff算法就包含在patchVnode函数里面:
这其中的diff过程中又分了好几种情况,oldCh为oldVnode的子节点,ch为Vnode的子节点:
新老节点一样,直接返回;
老节点有子节点,新节点没有:删除老节点的子节点;
老节点没有子节点,新节点有子节点:新节点的子节点直接append到老节点;
都只有文本节点:直接用新节点的文本节点替换老的文本节点;
都有子节点:updateChildren(这个函数应该是接下来最重要的)
patchVnode(oldVnode, vnode) // diff在这里
function patchVnode (oldVnode, vnode) {
const el = vnode.el = oldVnode.el // 找到对应的真实DOM
let oldCh = oldVnode.children, ch = vnode.children
if (oldVnode === vnode) return // 如果新老节点相同,直接返回
if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {
// 如果新老节点都有文本节点且不相等,那么新节点的文本节点替换老节点的文本节点
api.setTextContent(el, vnode.text)
}else {
updateEle(el, vnode, oldVnode)
if (oldCh && ch && oldCh !== ch) {
// 如果新老节点都有子节点,执行updateChildren比较子节点
updateChildren(el, oldCh, ch)
}else if (ch){
// 如果新节点有子节点而老节点没有子节点,那么将新节点的子节点添加到老节点上
createEle(vnode)
}else if (oldCh){
//如果新节点没有子节点而老节点有子节点,那么删除老节点的子节点
api.removeChildren(el)
}
}
}

整个流程下来,上面最开始的patch函数就讲解完了,那么patch函数什么时候触发呢???????????
当数据发生改变时,双向数据绑定中的set方法(详情请参考数据驱动章节)会让调用Dep.notify通知所有订阅者Watcher,订阅者就会调用patch给真实的DOM打补丁,进行页面的真正更新。
key
当我们在Vue组件中使用v-for循环的时候,假如我们在循环中间新增了一个数据,那么理论上虚拟DOM会在新增之后进行一个个节点的替换,其实这样很浪费,因为就是新增了一个节点,完全没必要前面替换,然后还需要新增一个节点,浪费时间,而且新旧对比还会发生没必要的补丁,更新真实DOM还费时间。
在Vue组件我们利用循环就会使用key,key在虚拟DOM里面绑定的是节点ID,然后把数据的ID给节点ID,这样子就可以使用diff算法时不会替换前面的循环,直接新增一个节点,然后真实DOM也就增加一个节点,省了好多时间。
Vue.nextTick
因为我们更改一段代码,让页面中的DOM进行更新时,节点因为虚拟DOM的存在不会马上就去更新,中间有一段比较的过程,所以读取会发现根本没有数据更改,这时候Vue.nextTick就出来了。
Vue.nextTick() 是一种异步操作函数,它会在真实DOM更新之后才执行回调函数。
准确来说就是函数返回一个Promise对象,之后就是then来回调访问真实DOM了。
1311



被折叠的 条评论
为什么被折叠?



