浅析 v-node
🌈 本节我们来探索一下虚拟DOM,将会从以下4个方面进行阐述:
- 虚拟DOM的本质
- 虚拟DOM的优势
- 虚拟DOM转换真实DOM 过程
- 虚拟DOM树 diff算法
虚拟DOM的本质
⭐️虚拟DOM 本质上是一个 js 对象,用于描述页面的结构,在 Vue 中,每个组件都有一个render函数,每一个 render 函数运行会返回一棵虚拟DOM树。
打印一下虚拟DOM👇

虚拟DOM的优势
- 当组件的 依赖数据 变化时,该组件需要重新渲染,去重新运行render函数,得到一棵新的虚拟DOM树,将新旧两棵树进行对比,通过 patch 算法,做到最小量更新。
- 如果每次渲染去对真实DOM的创建、更新、插入是非常消耗性能的,降低渲染效率,用虚拟DOM代替真实DOM,解决了渲染效率的问题。
虚拟DOM转换真实DOM 过程
- 当组件首次渲染时,它会先生成虚拟DOM树,然后根据虚拟DOM树创建真实DOM,并把真实DOM挂载到页面中合适的位置,此时,每个虚拟DOM便会对应一个真实的DOM。(首次渲染,虚拟DOM优势不是很大,比操作真实DOM多了一个构建虚拟DOM树的过程,在后续更新过程中,优势变得更大。)
- 如果组件不是首次渲染,依赖数据发生变化,则需要重新渲染,重新调用render函数,创建出一个新的虚拟DOM树,用新树和旧树对比,通过内部
patch函数,vue会找到最小更新量,然后更新必要的虚拟DOM节点,最后,这些更新过的虚拟节点,会去修改它们对应的真实DOM,做一些属性上的更新,保证了树的稳定和最小量更新。
虚拟DOM树 diff 算法
-
diff发生的时机是在组件实例创建时,以及依赖的属性或数据变化时,会运行一个函数,该函数会做两件事:
- 运行
_render生成一棵新的虚拟dom树(vnode tree) - 运行
_update,传入虚拟dom树的根节点,对新旧两棵树进行对比,最终完成对真实dom的更新
核心代码如下:
// vue构造函数 function Vue(){// ... 其他代码var updateComponent = () => {this._update(this._render())}// ... 其他代码 }diff就发生在_update函数的运行过程中 - 运行
-
_update函数在干什么_update函数接收到一个vnode参数,这就是新生成的虚拟dom树同时,
_update函数通过当前组件的_vnode属性,拿到旧的虚拟dom树_update函数首先会给组件的_vnode属性重新赋值,让它指向新树然后会判断旧树是否存在:
-
不存在:说明这是第一次加载组件,于是通过内部的
patch函数,直接遍历新树,为每个节点生成真实DOM,挂载到每个节点的elm属性上 -
存在:说明之前已经渲染过该组件,于是通过内部的
patch函数,对新旧两棵树进行对比,以达到下面两个目标:- 完成对所有真实dom的最小化处理
- 让新树的节点对应合适的真实dom
-
-
patch函数的对比流程- 相同:是指两个虚拟节点的标签类型、
key值均相同,但input元素还要看type属性,具体实现如下👇:
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)) || (isTrue(a.isAsyncPlaceholder) &&a.asyncFactory === b.asyncFactory &&isUndef(b.asyncFactory.error)))) } function sameInputType (a, b) {if (a.tag !== 'input') return truelet iconst typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.typeconst typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.typereturn typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB) }- 新建元素:根据一个虚拟节点提供的信息,创建一个真实dom元素,并且挂载到该虚拟节点的
elm属性上
fucntion create(){// 其他代码... // ns 表示 namespacevnode.elm = vnode.ns? nodeOps.createElementNS(vnode.ns, tag): nodeOps.createElement(tag, vnode)}- 销毁元素:通过
vnode.elm.remove()方法进行销毁 - 更新:两个虚拟节点进行对比更新,它仅发生在两个虚拟节点相同的情况下,做一些属性的更新
- 对比子节点:两个虚拟节点的子节点进行对比
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {// 为新旧两棵树分别创建首位两个指针,遍历的同时做 删除和新建操作let oldStartIdx = 0 let newStartIdx = 0let oldEndIdx = oldCh.length - 1let newEndIdx = newCh.length - 1// 首先判断 key值,tag类型,再做相应的 创建 删除 更新操作// 其他代码....}diff过程:
- 根节点对比
patch函数先对根节点进行对比
如果两个节点
- 相同,则进入更新流程
- 将旧节点的真实dom赋值到新节点:
newVnode.elm = oldVnode.elm - 对比新节点和旧节点的属性,有变化的更新到真实dom中
- 当前两个节点处理完毕,开始对比子节点
- 将旧节点的真实dom赋值到新节点:
- 不相同
⭐️ vue会认为这是两棵不一样的树,直接旧树不要,根据新树创建元素- 新节点递归 新建元素
- 旧节点销毁元素
- 对比子节点
在对比子节点时,vue一切的出发点,都是为了最小量更新,尽量做到只是元素的移动,属性的更新,实在不行只能新建和删除元素。
- 相同:是指两个虚拟节点的标签类型、

















