原文链接我的blog,欢迎STAR。
这次和想要大家分享的一篇文章解析Vue diff 算法
在上篇里,我们提到在渲染时,render
>template
>el
,但是最终,我们得到的都是render
函数,那么render
函数的作用是什么?接下来该干什么?
带着两个问题,我们深入源码。
首先来解决第一个问题:
-
render
函数的作用是什么?在/src/core/instance/lifecycle.js中有这么一段代码:
vm._watch = new Watch(vm, updateComponent, noop)
然后找到updateComponent
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
意思是,通过Watcher
的绑定,当数据发生变化时,会执行updateComponent
方法,根据不同的条件,调用的updateComponent
也不同,在开发环境下,主要调用的是else
里的updateComponent
方法,意思是在执行vm._update
方法之前会先执行vm._render()
方法。
接着找到vm._render()
方法,在src/core/instance/render.js文件里,有这么一段代码:
在vm._render
方法里,会得到一个VNode对象。至此,第一个问题解决,但是VNode, 又是什么?将在解决第二个问题时,解决。
- 在已经知晓
render
函数会得到VNode对象,那么VNode
又是什么,下一步又将做什么?
关于VNode, 其实就是Vue.js 2.0 的 Virtual DOM
对象,
在/src/core/vdom/vnode.js中,可以看到
VNode具体的一些数据:
这些数据,既是对DOM节点的一些描述。
如果你有兴趣,可以做个实验,瞧瞧真实DOM对象,
const div = document.createElement('div')
for (let k in div) {
console.log(k)
}
倘若每次都生成一个DOM,对性能该是多大的浪费。
于是我们总结出VNode其实Virtual DOM
对象,就是用一个简单一点的对象去代替复杂的dom对象。生成VNode,Virtual DOM
对象之后,接下来,需要通过DOM Diff,生成真正的DOM节点。
关于DOM diff 说简单点,其实就是所有的变动先发生在Virtual DOM
对象上,然后再将实际发生改变的部分反应到真实DOM上。
值得注意的一点是,比较只会在同级进行,并不会跨层级比较
引用来自React’s diff algorithm的一张图:
分析源码,在/src/core/vdom/patch.js的patch方法中:
function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
// ...
}
patch函数接收六个参数:
oldVnode
: 旧的Virtual DOM
或者旧的真实DOM;
vnode
:新的Virtual DOM
;
hydrating
: 是否与真实DOM混合;
removeOnly
: 这个在源码里有提到,removeOnly is a special flag used only by <transition-group>
也就是说是特殊的flag,用于transition-group组件;
parentElm
: 父节点;
refElm
: 新节点插入到refElm之前。
分析具体实现思路:
- 如果
vnode
不存在,oldVnode
存在,此时需要销毁oldVnode
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestoryHook(oldVnode)
return
}
invokeDestroyHook()
方法也就是来销毁节点的一个方法。
- 如果
oldVnode
不存在,vnode
存在,需要调用createElm()
方法创建新节点
if (isUndef(oldVnode)) {
isInitialpatch = true
createElm(vnode, inserteVnodeQueue, parentElm, refElm)
}
isInitialpatch = true
主要用来作延迟插入处理。
-
如果
vnode
与oldVnode
都存在:- 如果
oldVnode
不为真实节点,且oldVnode
与vnode
是同一节点,那么调用patchVnode()
方法,(patchVnode
稍后详细讲解)。
const isRealElement = isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(old, vnode)) { patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) }
- 如果不为同一节点时,当
oldVnode
是真实节点,并且是元素节点,且含有server-render
属性的时候,移除server-render
属性,把hydrating
设置为true
,并调用hydrate
函数,将Virtual DOM
与真实DOM之间进行映射,然后将oldVnode
设置为对应的Virtual DOM
,找到oldVnode.elm的父节点,根据vnode
创建一个真实DOM节点,并插入到该父节点的oldVnode.elm的位置。
- 如果
最后返回
vnode.elm
, 那么和最开始进如的vnode.el 有什么区别在哪?
其实很简单,因为最开始作为参数传进去的是新的Virtual DOM
对象,也就是说不是真实DOM,再具体点,就是 vnode.elm = null. 经过函数返回以后,现在引用的是对应的真实dom。
至此,完成一个patch过程。
下篇里,将从源码解析patchVnode()
以及updateChildren()
方法。
完。