else if (isTrue(vnode.isComment)) { vnode.elm = nodeOps.createComment(vnode.text); insert(parentElm, vnode.elm, refElm); } else { vnode.elm = nodeOps.createTextNode(vnode.text); insert(parentElm, vnode.elm, refElm); }
如果是评论节点,直接创建评论节点,并将其插入到父节点上,其他的创建文本节点,并将其插入到父节点parentElm(刚创建的div)上去。 触发钩子,更新节点属性,将其插入到parentElm('#app'元素节点)上
{ createChildren(vnode, children, insertedVnodeQueue); if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue); } insert(parentElm, vnode.elm, refElm); }
最后将老的节点删掉
if (isDef(parentElm)) { removeVnodes(parentElm, [oldVnode], 0, 0); } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode); }
function removeAndInvokeRemoveHook (vnode, rm) { if (isDef(rm) || isDef(vnode.data)) { var i; var listeners = cbs.remove.length + 1; ... // recursively invoke hooks on child component root node if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) { removeAndInvokeRemoveHook(i, rm); } for (i = 0; i < cbs.remove.length; ++i) { cbs.remove[i](vnode, rm); } if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) { i(vnode, rm); } else { // 删除id为app的老节点 rm(); } } else { removeNode(vnode.elm); } }
初次渲染结束。
更新节点过程
为了更好地测试,模板选用
<div>{{ message }}<button @click="update">更新</button></div>
点击按钮,会更新message,重新渲染视图,生成的VNode为
{ asyncFactory: undefined, asyncMeta: undefined, children: [VNode, VNode], componentInstance: undefined, componentOptions: undefined, context: Vue实例, data: {attrs: {id: "app"}}, elm: undefined, fnContext: undefined, fnOptions: undefined, fnScopeId: undefined, isAsyncPlaceholder: false, isCloned: false, isComment: false, isOnce: false, isRootInsert: true, isStatic: false, key: undefined, ns: undefined, parent: undefined, raw: false, tag: "div", text: undefined, child: undefined }
在组件更新的时候,preVnode和vnode都是存在的,执行
vm.$el = vm.__patch__(prevVnode, vnode);
实际上是运行以下函数
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
该函数首先判断oldVnode和vnode是否相等,相等则立即返回
if (oldVnode === vnode) { return }
如果两者均为静态节点且key值相等,且vnode是被克隆或者具有isOnce属性时,vnode的组件实例componentInstance直接赋值
if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance; return }
接着对两者的属性值作对比,并更新
var oldCh = oldVnode.children; var ch = vnode.children; if (isDef(data) && isPatchable(vnode)) { for (i = 0; i < cbs.update.length; ++i) { // 以vnode为准更新oldVnode的不同属性 cbs.update[i](oldVnode, vnode); } if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); } }
vnode和oldVnode的对比以及相应的DOM操作具体如下:
// vnode不存在text属性的情况 if (isUndef(vnode.text)) { if (isDef(oldCh) && isDef(ch)) { // 子节点不相等时,更新 if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); } } else if (isDef(ch)) { { checkDuplicateKeys(ch); } // 只存在vnode的子节点,如果oldVnode存在text属性,则将元素的文本内容清空,并新增elm节点 if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); } else if (isDef(oldCh)) { // 如果只存在oldVnode的子节点,则删除DOM的子节点 removeVnodes(elm, oldCh, 0, oldCh.length - 1); } else if (isDef(oldVnode.text)) { // 只存在oldVnode有text属性,将元素的文本清空 nodeOps.setTextContent(elm, ''); } } else if (oldVnode.text !== vnode.text) { // node和oldVnode的text属性都存在且不一致时,元素节点内容设置为vnode.text nodeOps.setTextContent(elm, vnode.text); }
对于子节点的对比,先分别定义oldVnode和vnode两数组的前后两个指针索引
var oldStartIdx = 0; var newStartIdx = 0; var oldEndIdx = oldCh.length - 1; var oldStartVnode = oldCh[0]; var oldEndVnode = oldCh[oldEndIdx]; var newEndIdx = newCh.length - 1; var newStartVnode = newCh[0]; var newEndVnode = newCh[newEndIdx]; var oldKeyToIdx, idxInOld, vnodeToMove, refElm;
如下图:
接下来是一个while循环,在这过程中,oldStartIdx、newStartIdx、oldEndIdx 以及 newEndIdx 会逐渐向中间靠拢
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx)
当oldStartVnode或者oldEndVnode为空时,两中间移动
if (isUndef(oldStartVnode)) { oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left } else if (isUndef(oldEndVnode)) { oldEndVnode = oldCh[--oldEndIdx]; }
接下来这一块,是将 oldStartIdx、newStartIdx、oldEndIdx 以及 newEndIdx 两两比对的过程,共四种: