patch
将虚拟DOM渲染成DOM,这就是patch的作用
在vue运行的时候会生成新旧两个虚拟DOM树,通过比较这两棵DOM树,我们就能针对性的修改真实DOM
事实上,我们大可以在每次比较两棵DOM树的时候删除现有的DOM结构,然后根据新的虚拟DOM来渲染最新的DOM,但这样做的结果就是性能开销过大,所以vue并没有采取这种方式,vue会比对新旧两个vnode之间有哪些不同,然后根据对比结果找出需要更新的节点进行更新
我们知道DOM的操作对比JavaScript的操作来说是十分消耗性能的,因此我们完全可以将大部分的DOM操作放在JavaScript中,通过各种算法来得到需要更新的DOM节点,也就是通过虚拟DOM的比对,也就是patching算法
在patch中针对虚拟DOM的比对主要分为三部分
新增节点
首次渲染
新增节点最明显的一个应用场景就是首次渲染,因为首次渲染没有oldVNode,只有newVNode,当只有newVNode时patch会直接根据newVNode来生成视图
节点不同
除了首次渲染,当两个对应位置的VNode完全不同时,patch也会新增节点,用这个新节点来生成DOM以替换原来的旧节点
创建节点
事实上,只有三种节点会被插入到视图中,分别是元素节点,注释节点,文本节点
我们可以通过调用不同的创建函数来得到不同的节点,如果一个节点有子节点的话那么将会递归创建,创建子节点时,子节点的父节点就是当前刚创建出来的这个节点,所以子节点被创建后,会被插入到当前节点的下面,当所有子节点都创建并插入完毕后如果父节点已经被渲染到视图中,那么将子节点插入进去之后,会将所有节点都渲染到视图中
插入节点
patch会根据不同的节点使用不同的插入方式:
- 元素节点
patch会调用当前环境下的appendChild方法将指定节点插入到指定父元素之下 - 文本节点
patch会调用当前环境下的createTextNode方法 - 注释节点
patch会调用当前环境下的createComment方法
删除节点
删除节点的场景很简单,即如果当前节点不在newVNode中,patch就会将它删除,具体过程是将新创建的DOM节点插入到旧节点的旁边,然后再将旧节点删除,从而完成替换过程
更新节点
静态节点
在更新节点之前,patch会判断当前节点是否是静态节点,如果是的话就跳过更新步骤
静态节点即渲染到视图上后永远不会改变的节点
<p>我是静态节点,我不需要发生变化</p>
文本节点
如果新旧两个VNode的文本不一致,则以newVNode为准调用setTextContent方法
元素节点
如果newVNode有children的话则会判断oldVNode有无children,如果没有则正常创建DOM,有的话则会通过diff来进行更加细致的比对
如果newVNode无children的话这说明这个新创建的节点是一个空节点,则在DOM中有什么删什么就行,达到视图中是空标签的目的
diff
我们通过循环newChildren来对比两个子节点列表,每从newChildren中取出一个VNode我们就去oldChildren中对比,如果匹配成功,则做更新操作,如果没有找到就做新增操作,如果两个节点的位置不同,则移动节点
插入子节点
diff通过循环newChildren,每次循环拿出一个节点来和oldChildren对比,如果是新增节点那么就会插入到所有未处理节点的前面
需要注意的是,我们插入子节点时的位置是在oldChildren中所有未处理节点前对应的位置,而不是oldChildren中所有已处理节点后对应的位置
因为我们是使用虚拟节点进行对比,而不是真实DOM节点做对比,插入的位置也是由两个节点列表对比得到,如果插入到已处理节点之后的话会导致顺序混乱
删除节点
删除子节点,本质上是删除那些oldChildren中存在但newChildren中不存在的节点
即当newChildren中的所有节点都被循环了一遍后,如果oldChildren中还有剩余的没有被处理的节点,那么这些节点就是需要删除的节点
更新节点
最后我们来更新节点,如果一个新旧两个节点位置相同且是同一个节点,那么直接进行更新节点操作,如果位置不一致的话我们还需要移动节点
移动节点
我们以newChildren为基准,将真实DOM中为同一节点的节点通过insertBefore方法移动到指定位置,那么问题来了,我们如何确定将节点移动到哪里呢
我们比对节点列表时是通过for循环遍历newChildren的,那么就意味着当前所遍历到的项的前面所有节点都是处理好的,我们只需要将指定的节点(真实DOM)插入到所有未处理节点之前(相对于oldChildren)就行