虚拟dom变成真实dom,用的就是diff算法
1. diff的时机
当组件创建时(new Vue或者使用自定义组件,render时形成虚拟结点,创建组件对象),以及依赖的属性(props)或数据(data)变化时,会运行一个函数,该函数会做两件事:
- 运行
_render
生成一棵新的虚拟dom树(vnode tree) - 运行
_update
,传入虚拟dom树的根节点,对新旧两棵树进行对比,最终完成对真实dom的更新
核心代码如下:
// vue构造函数
function Vue(){
// ... 其他代码
var updateComponent = () => {
this._update(this._render())//调用组件update方法,传入用render创建的一个参数,this._render()就是运行render函数,返回虚拟dom,
}
new Watcher(updateComponent);//watcher使它变成响应式
// ... 其他代码
}
diff
就发生在_update
函数的运行过程中
2. _update函数在干什么
function _update(vnode){
var oldVnode=this,_vnode;//this是当前组件(created打印this可看),_vnode始终指向render返回的结果,保存老的虚拟结点
this._node=vnode;//新的覆盖,新的虚拟结点
}
_update
函数接收到一个vnode
参数,这就是新生成的虚拟dom树
同时,_update
函数通过当前组件的_vnode
属性,拿到旧的虚拟dom树
_update
函数首先会给组件的_vnode
属性重新赋值,让它指向新树
然后会判断旧树是否存在:
-
不存在:说明这是第一次加载组件,于是通过内部的
patch
函数,直接遍历新树,为每个节点生成真实DOM,挂载到每个节点的elm
属性上
源码↑ -
存在:说明之前已经渲染过该组件,于是通过内部的
patch
函数,对新旧两棵树进行对比,以达到下面两个目标(这个时候不需要改变虚拟dom,因为现在的虚拟dom已经指向正确(this._node=vnode)):- 完成对所有真实dom的最小化处理
- 让新树的节点对应合适的真实dom
一个是增删,一个是加上elm
3. patch函数的对比流程
术语解释:
- 「相同」:是指两个虚拟节点的标签类型(tag属性)、
key
值均相同(满足这两个条件就是同一个虚拟结点),但input
元素还要看type
属性
源码↑
2. 「新建元素」:是指根据一个虚拟节点提供的信息,创建一个真实dom元素,同时挂载到虚拟节点的elm
属性上
- 「销毁元素」:是指:
vnode.elm.remove()
- 「更新」:是指对两个虚拟节点进行对比更新,它仅发生在两个虚拟节点「相同」的情况下。具体过程稍后描述。
不相同 - 「对比子节点」:是指对两个虚拟节点的子节点进行对比,具体过程稍后描述
详细流程:
-
根节点比较
patch
函数首先对根节点进行比较如果两个节点:
-
「相同」,进入**「更新」流程**
- 将旧节点的真实dom赋值到新节点:
newVnode.elm = oldVnode.elm
- 对比新节点和旧节点的属性,有变化的更新到真实dom中
- 当前两个节点处理完毕,开始**「对比子节点」**
- 将旧节点的真实dom赋值到新节点:
-
不「相同」
- 新节点递归「新建元素」
- 旧节点「销毁元素」
-
-
「对比子节点」
在「对比子节点」时,vue一切的出发点,都是为了:
- 尽量啥也别做
- 不行的话,尽量仅改动元素属性
- 还不行的话,尽量移动元素,而不是删除和创建元素
- 还不行的话,删除和创建元素
双指针,每个数组里面头尾有两个指针,首先比对头指针,相同进入更新流程
头指针后移,不相同,这时候看尾指针,相同,更新
头尾不相同,对比头尾相同
位置移动
此时头尾找不到相同结点,拿下面的3去映射上面,映射地图
真实dom插在6前
移动头指针,和上面尾指针匹配,9的真实dom插6前
都不相同,新建8真实dom,放在上面头指针指向之前,即6前。下面头指针往后,头大于尾,循环结束。此时旧结点还没处理完,直接循环删除。删除旧数列。
之所以不推荐index做key值,其实跟没有标key值是差不多的
应用
如果想要两个文本框不复用,分别加key值