我們打開文件 src/core/vdom/patch.js
,找到定義Observer
函數(shù)的代碼:
return function patch (oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
// 如果沒有老的node節(jié)點,就用新的vnode節(jié)點去創(chuàng)建。
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else {
if (isRealElement) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
} else if (process.env.NODE_ENV !== 'production') {
warn(
'The client-side rendered virtual DOM tree is not matching ' +
'server-rendered content. This is likely caused by incorrect ' +
'HTML markup, for example nesting block-level elements inside ' +
'<p>, or missing <tbody>. Bailing hydration and performing ' +
'full client-side render.'
)
}
}
// either not server-rendered, or hydration failed.
// create an empty node and replace it
oldVnode = emptyNodeAt(oldVnode)
}
// replacing existing element
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// create new node
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// update parent placeholder node element, recursively
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor)
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
// destroy old node
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
}
首先如果最新值是 undefined
或者 null
并且舊值不是undefined
或者 null
, 那就調(diào)用 invokeDestroyHook
函數(shù)(解綁它的指令及事件監(jiān)聽器)。
接下來我們詳細(xì)分析
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
如果沒有老的node節(jié)點,就用新的vnode節(jié)點去創(chuàng)建。createElm
是創(chuàng)建節(jié)點的函數(shù)。
接下來就是存在 oldVnode
的情況,
const isRealElement = isDef(oldVnode.nodeType)
首先判斷是否是真實節(jié)點 ,因為每個幾都有 nodeType
屬性具體的節(jié)點類型請點擊查看。
if (isRealElement) {
// 省略部分代碼
oldVnode = emptyNodeAt(oldVnode)
}
省略的都是關(guān)于 ssr
判斷是代碼,瀏覽器端的話就不用管了。
最后看到,就新建一個空的新節(jié)點賦值給了 oldVnode
。
// replacing existing element
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// create new node
createElm(vnode,oldElm._leaveCb ? null : parentElm,nodeOps.nextSibling(oldElm))
定義了 oldElm
和 parentElm
分別保存舊的真實DOM節(jié)點和舊的真實DOM節(jié)點的父節(jié)點。
然后就是調(diào)用 createElm
函數(shù)創(chuàng)建節(jié)點了。可以看到真實的DOM節(jié)點被插入到頁面了。
// destroy old node
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
移除舊的DOM節(jié)點,解綁它的指令及事件監(jiān)聽器。
最后返回真實的DOM節(jié)點給 $el
屬性。