patch介绍
虚拟DOM最核心的部分是patch,它将vnode渲染成真实DOM过程中并不是暴力覆盖原有DOM,而是对比两个vnode之间有哪些不同,然后根据对比结果找出需要更新的节点进行更新。这么做的原因是因为DOM操作的执行速度远不如JavaScript运算速度快,利用这一点在,相比起直接进行大量DOM操作之前,可以通过patching算法计算出真正需要更新的节点,然后进行少量DOM操作即可,从而提高性能。
对比两个vnode之间的差异只是patch的一部分,这是手段而不是目的,patch的目的在于修改DOM节点,也就是渲染视图。patch对现有DOM进行修改需要做三件事:
1、创建新增节点
2、删除已废弃的节点
3、修改需要更新的节点
下面要讨论什么情况下创建新节点,删除节点及修改节点:
新增节点
事实上,只有那些因为状态的改变而新增的节点在DOM中并不存在时,我们才需要创建一个节点并插入DOM中。
新增有两种场景:
1、oldVNode不存在而vnode存在,通常发生在首次渲染时
2、vnode和oldVnode完全不是同一个节点时
删除节点
对应上面提到的场景2,在创建新节点替换对应旧节点后将其从旧节点删除,从而完成替换过程
更新节点
这个容易理解,oldVnode和vnode是同一节点,只是一些属性发生了变化,如文本内容发生了变化。
通过上面的讲解已经掌握了patch流程,下面将分别详细讲解新增节点、删除节点和更新节点的实现细节。
创建节点
我们知道创建一个真实DOM元素所需的信息保存在vnode中,我们需要vnode来创建一个真实的DOM元素,而在创建的过程中其实是依赖vnode类型来创建对应类型的DOM元素的。
事实上只有三个类型节点会被创建并插入到DOM中:元素节点、注释节点和文本节点。
1、判断vnode是否元素节点,只需要判断它是否具有tag属性,如果存在就调用createElement方法创建DOM,然后调用appendChild方法插入到指定父元素中。元素节点通常会有字节点,所以当一个元素节点被创建后,我们需要将它的字节点也创建出来并插入到这个刚创建出来的节点下面。vnode中的children属性保存了当前节点所有字虚节点(child virtual node),只需要递归虚幻一遍children属性,将每个子虚节点都执行一遍创建元素的逻辑即可。
2、 如果vnode中不存在tag属性,则可能是注释节点和文本节点。此时可以再根据isComment属性判断出注释节点(该属性为true时)和文本节点。创建DOM元素方法分别是createComment和createTextNode
删除节点
vue中删除元素代码如下:
function removeVnodes (vnodes, startIdx, endIdx) {
for(; startIdx <= endIdx; ++startIdx) {
const ch = vnodes[startIdx]
if (isDef(ch)) {
removeVnodes(ch.elm)
}
}
}
上面的代码实现的功能是删除vnodes数组中从startIdx到endIdx指定位置的内容。
removeNode用于删除视图中单个节点,而removeVnodes用于删除一组指定节点。
removeNode实现逻辑如下:
const nodeOps = {
removeChild (node, child) {
node.removeChild(child)
}
}
function removeNode (el) {
const parent = nodeOps.parentNode(el)
if (isDef(parent)) {
nodeOps.removeChild(parent, el)
}
}
为什么这里使用nodeOps而不直接使用parent。removeChild(child)删除节点呢?这是涉及跨平台渲染的原因,要让框架的渲染机制和DOM解耦,在不同平台调用对应的节点操作。
更新节点
在更新节点时,首先需要判断新旧两个虚拟节点是否静态节点,如果是,就直接跳过更新节点过程。那静态节点是什么意思?静态节点是指那些一旦渲染到页面上,无论状态如何变化,都不会发生变化的节点。
例如:
我是一个标题,我是不会改变的
当新旧两个虚拟节点不是静态节点,并且有不同属性时,则以vnode为准更新视图。如果有text属性,直接调用setTextContent将视图中节点内容改为text属性内容。
如果没有text属性,那么它就是一个元素节点。元素节点通常会有字节点,也就是children属性,也可能没有,所以存在两种情况:
1、有children:
这时其实还会存在两种情况,就是看旧的虚拟节点是否有children属性,如果旧的也存在,则要对新旧两个children进行一个更详细的对比更新。如果没有,说明旧虚拟节点是一个空标签或者文本节点。
2、无children:
新创建的虚拟节点既没有text属性也没有children属性,说明是一个空节点,这时如果旧的虚拟节点存在chidlren则删除,最后达到视图是一个空标签的目的。