模板tamplate
通过parse
,optimize
,generate
等一些列操作之后,把AST
转为render function code
进而生成虚构VNode
,模板编译阶段根本曾经实现了,那么这一章,咱们来探讨一下Vue
中的一个算法策略–dom diff
首先来介绍下什么叫dom diff
什么是虚构dom
咱们通过后面的章节学习曾经晓得,要晓得渲染实在DOM
的开销是很大的,比方有时候咱们批改了某个数据,如果间接渲染
到实在dom上会引起整个dom树
的重绘
和重排
,有没有可能咱们只更新咱们批改的那一小块dom而不要更新整个dom呢?
为了解决这个问题,咱们的解决方案是–依据实在DOM
生成一颗virtual DOM
,当virtual DOM
某个节点的数据扭转后会生成一个新的Vnode
,而后Vnode和oldVnode
作比照,发现有不一样的中央就间接批改在实在的DOM
上,而后使oldVnode
的值为Vnode
。这也就是咱们所说的一个虚构dom diff
的过程
图示
传统的Diff算法所消耗的工夫复杂度为O(n^3)
,那么这个O(n^3)
是怎么算进去的?
- 传统diff算法工夫复杂度为n(第一次Old与新的所有节点比照)—-
O(n)
- 传统diff算法工夫复杂度为n(第二次Old树的所有节点与新的所有节点比照)—-
O(n^2)
- 新树的生成,节点可变编辑,工夫复杂度为n(遍历以后树)—-
O(n^3)
第一次比照 (1:n)
第二次比照 (1:n)
第n次比照 (n:n)
到这里那么n个节点与n个节点暴力比照就比照完了,那么就开启第三轮可编辑树节点遍历,更改之后的树由vdom(old)
到vdom(new)
故而传统diff算法O(n^3)
是这么算进去的,然而这不是咱们明天钻研的重点。
古代diff算法
古代diff算法
策略说的是,同层级比拟,广度优先
那么这里的话咱们要深刻源码了,在深刻源码之前咱们在心中应该造成这样一个概念,整个diff的流程是什么?咱们再比照着源码解读
diff算法流程图
深刻源码
咱们在Vue初始化的时候调用lifecycleMixin
函数的时候,会给Vue
的原型上挂载_update
办法
_update
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
if (vm._isMounted) {
//会调用申明周期中的beforeUpdate回调函数
callHook(vm, 'beforeUpdate')
}
const prevEl = vm.$el
const prevVnode = vm._vnode
const prevActiveInstance = activeInstance
activeInstance = vm
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
//若组件自身的vnode未生成,间接用传入的vnode生成dom
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false /* removeOnly */,
vm.$options._parentElm,
vm.$options._refElm
)
// no need for the ref nodes after initial patch
// this prevents keeping a detached DOM tree in memory (#5851)
vm.$options._parentElm = vm.$options._refElm = null
} else {
//对新旧vnode进行diff
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
咱们在这里能够看到vm.$el = vm.__patch__
办法,追根溯源_patch_
的定义:
Vue.prototype.__patch__ = inBrowser ? patch : noop
可见这里是一个浏览器环境的甄别,如果在浏览器环境中,咱们会执行patch,不在的话会执行noop,这是一个util外面的一个办法,用来跨平台的,咱们这里临时不思考,接着咱们去看patch的具体实现./patch
文件,参考vue实战视频解说:进入学习
import * as nodeOps from 'web/runtime/node-ops'
import { createPatchFunction } from 'core/vdom/patch'
import baseModules from 'core/vdom/modules/index'
import platformModules from 'web/runtime/modules/index'
const modules = platformModules.concat(baseModules)
export const patch: Function = createPatchFunction({ nodeOps, modules })
createPatchFunction函数
/** * 创立patch办法 */
export function createPatchFunction (backend) {
let i, j
const cbs = {}
const { modules, nodeOps } = backend
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = []
for (j = 0; j < modules.length; ++j) {
if (isDef(modules[j][hooks[i]])) {
cbs[hooks[i]].push(modules[j][hooks[i]])
}
}
}
function emptyNodeAt (elm) {
return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
}
/** * 创立一个回调办法, 用于删除节点 * * */
function createRmCb (childElm, listeners) {
function remove () {
if (--remove.listeners === 0) {
removeNode(childElm)
}
}
remove.listeners = listeners
return remove
}
function removeNode (el) {
const parent = nodeOps.parentNode(el)
// element may have already been removed due to v-html / v-text
if (isDef(parent)) {
nodeOps.removeChild(parent, el)
}
}
/** * 通过vnode的tag判断是否是原生dom标签或者组件标签 * 用于创立实在DOM节点时, 预先判断tag的合法性 */
function isUnknownElement (vnode, inVPre) {
return (
!inVPre &&
!vnode.ns &&
!(
config.ignoredElements.length &&
config.ignoredElements.some(ignore => {
return isRegExp(ignore)
? ignore.test(vnode.tag)
: ignore === vnode.tag
})
) &&
config.isUnknownElement(vnode.tag)
)
}
let creatingElmInVPre = 0
// 创立一个节点
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
// 节点曾经被渲染, 须要应用一个克隆节点
if (isDef(vnode.elm) && isDef(ownerArray)) {
// This vnode was used in a previous render!
// now it's used as a new node, overwriting its elm would cause
// potential patch errors down the road when it's used as an insertion
// reference node. Instead, we clone the node on-demand before creating
// associated DOM element for it.
vnode = ownerArray[index] = cloneVNode(vnode)
}
// 创立组件节点 详见本文件中的createComponent办法
vnode.isRootInsert = !nested // for transition enter check
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
/** * 如果要创立的节点有tag属性, 这里做一下校验 * 如果该节点下面有v-pre指令, 间接给flag加1 * 如果没有v-pre须要调用isUnknownElement判断标签是否非法, 而后给出正告 */
if (isDef(tag)) {
if (process.env.NODE_ENV !== 'production') {
if (data && data.pre) {
creatingElmInVPre++
}
if (isUnknownElement(vnode, creatingElmInVPre)) {
warn(
'Unknown custom element: <' + tag + '> - did you ' +
'register the component correctly? For recursive components, ' +
'make sure to provide the "name" option.',
vnode.context
)
}
}
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
/* istanbul ignore if */
if (__WEEX__) {
// in Weex, the default insertion order is parent-first.
// List items can be optimized to use children-first insertion
// with append="tree".
const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
if (!appendAsTree) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
createChildren(vnode, children, insertedVnodeQueue)
if (appendAsTree) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
} else {
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
if (process.env.NODE_ENV !== 'production' && data && data.pre) {
creatingElmInVPre--
}
} 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)
}
}
/** * 创立组件 * 如果组件实例曾经存在, 只须要初始化组件并从新激活组件即可 */
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */, parentElm, refElm)
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
/** * 初始化组件 * 次要的操作是已插入的vnode队列, 触发create钩子, 设置style的scope, 注册ref */
function initComponent (vnode, insertedVnodeQueue) {
if (isDef(vnode.data.pendingInsert)) {
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
vnode.data.pendingInsert = null
}
vnode.elm = vnode.componentInstance.$el
if (isPatchable(vnode)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
setScope(vnode)
} else {
// empty component root.
// skip all element-related modules except for ref (#3455)
registerRef(vnode)
// make sure to invoke the insert hook
insertedVnodeQueue.push(vnode)
}
}
/** * 激活组件 */
function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i
// hack for #4339: a reactivated component with inner transition
// does not trigger because the inner node's created hooks are not called
// again. It's not ideal to involve module-specific logic in here but
// there doesn't seem to be a better way to do it.
let innerNode = vnode
while (innerNode.componentInstance) {
innerNode = innerNode.componentInstance._vnode
if (isDef(i = innerNode.data) && isDef(i = i.transition)) {
for (i = 0; i < cbs.activate.length; ++i) {
cbs.activate[i](emptyNode, innerNode)
}
insertedVnodeQueue.push(innerNode)
break
}
}
// unlike a newly created component,
// a reactivated keep-alive component doesn't insert itself
insert(parentElm, vnode.elm, refElm)
}
/** * 插入节点, 有父节点的插入到后面, 没有的插入到前面 */
function insert (parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
if (ref.parentNode === parent) {
nodeOps.insertBefore(parent, elm, ref)
}
} else {
nodeOps.appendChild(parent, elm)
}
}
}
function createChildren (vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(children)
}
for (let i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
}
} else if (isPrimitive(vnode.text)) {
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
}
}
function isPatchable (vnode) {
while (vnode.componentInstance) {
vnode = vnode.componentInstance._vnode
}
return isDef(vnode.tag)
}
function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode)
}
i = vnode.data.hook // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) i.create(emptyNode, vnode)
if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
}
}
// set scope id attribute for scoped CSS.
// this is implemented as a special case to avoid the overhead
// of going through the normal attribute patching process.
function setScope (vnode) {
let i
if (isDef(i = vnode.fnScopeId)) {
nodeOps.setStyleScope(vnode.elm, i)
} else {
let ancestor = vnode
while (ancestor) {
if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {
nodeOps.setStyleScope(vnode.elm, i)
}
ancestor = ancestor.parent
}
}
// for slot content they should also get the scopeId from the host instance.
if (isDef(i = activeInstance) &&
i !== vnode.context &&
i !== vnode.fnContext &&
isDef(i = i.$options._scopeId)
) {
nodeOps.setStyleScope(vnode.elm, i)
}
}
function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) {
for (; startIdx <= endIdx; ++startIdx) {
createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm, false, vnodes, startIdx)
}
}
// 递归调用销毁钩子
function invokeDestroyHook (vnode) {
let i, j
const data = vnode.data
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
}
if (isDef(i = vnode.children)) {
for (j = 0; j < vnode.children.length; ++j) {
invokeDestroyHook(vnode.children[j])
}
}
}
/** * 删除多个节点 * 文本节点能够间接删除, 其余节点须要触发两个钩子 */
function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
const ch = vnodes[startIdx]
if (isDef(ch)) {
if (isDef(ch.tag)) {
removeAndInvokeRemoveHook(ch)
invokeDestroyHook(ch)
} else { // Text node
removeNode(ch.elm)
}
}
}
}
function removeAndInvokeRemoveHook (vnode, rm) {
if (isDef(rm) || isDef(vnode.data)) {
let i
const listeners = cbs.remove.length + 1
if (isDef(rm)) {
// we have a recursively passed down rm callback
// increase the listeners count
rm.listeners += listeners
} else {
// directly removing
rm = createRmCb(vnode.elm, listeners)
}
// 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 {
rm()
}
} else {
removeNode(vnode.elm)
}
}
// diff操作外围算法
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
// 记录新旧节点列表的首尾元素 用于比拟
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
// removeOnly is a special flag used only by <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
// 在transition中 不能挪动节点
const canMove = !removeOnly
// 查看是否有反复的key
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(newCh)
}
// 一共分四种状况探讨, 旧列表第一个与新列表第一个比照, 旧列表最初一个与新列表最初一个比照
// 而后新列表第一个和旧列表最初一个比照, 新列表最初一个和旧列表第一个比照
// 之所以要穿插头尾比照, 是为了避免最差的状况呈现
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
// 以上四种状况都不满足时, 应用新列表第一个vdom的key去旧列表查找
// 如果能够找到key雷同的元素, 间接进行patch而后进入下一次循环
// 找不到则插入一个新节点
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
// 新旧列表其中之一全副循环实现后, 开始清理残余的节点
// 如果旧列表全副遍历实现, 新列表还有残余, 间接创立这些新节点
// 反之, 如果新列表全副遍历, 旧列表还有残余, 间接删除这些旧节点
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
/** * 查看是否有反复的key * 一个很简略的遍历查找反复值的操作 * 其实这个seenKeys我感觉改成数组会更好, 写成object又给每个key的value置为true蛮奇怪的 */
function checkDuplicateKeys (children) {
const seenKeys = {}
for (let i = 0; i < children.length; i++) {
const vnode = children[i]
const key = vnode.key
if (isDef(key)) {
if (seenKeys[key]) {
warn(
`Duplicate keys detected: '${key}'. This may cause an update error.`,
vnode.context
)
} else {
seenKeys[key] = true
}
}
}
}
/** * 在旧的子节点列表寻找类似节点(只查找第一个) */
function findIdxInOld (node, oldCh, start, end) {
for (let i = start; i < end; i++) {
const c = oldCh[i]
if (isDef(c) && sameVnode(node, c)) return i
}
}
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
// 如果oldVnode跟vnode完全一致,那么不须要做任何事件
if (oldVnode === vnode) {
return
}
const elm = vnode.elm = oldVnode.elm
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
} else {
vnode.isAsyncPlaceholder = true
}
return
}
// 如果oldVnode跟vnode都是动态节点,且具备雷同的key
// 当vnode是克隆节点或是v-once指令管制的节点时
// 只须要把oldVnode.elm和oldVnode.child都复制到vnode上,也不必再有其余操作
// reuse element for static trees.
// note we only do this if the vnode is cloned -
// if the new node is not cloned it means the render functions have been
// reset by the hot-reload-api and we need to do a proper re-render.
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
const oldCh = oldVnode.children
const ch = vnode.children
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
// 如果vnode不是文本节点或正文节点
if (isUndef(vnode.text)) {
// 如果oldVnode和vnode都有子节点,且2方的子节点不完全一致,就执行updateChildren
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) {
// 如果只有vnode有子节点,那就创立这些子节点
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
// 如果只有oldVnode有子节点,那就把这些节点都删除
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
// 如果oldVnode和vnode都没有子节点,然而oldVnode是文本节点或正文节点,就把vnode.elm的文本设置为空字符串
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, '')
}
// 如果vnode是文本节点或正文节点,然而vnode.text != oldVnode.text时,只须要更新vnode.elm的文本内容即可
} else if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm, vnode.text)
}
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}
function invokeInsertHook (vnode, queue, initial) {
// delay insert hooks for component root nodes, invoke them after the
// element is really inserted
if (isTrue(initial) && isDef(vnode.parent)) {
vnode.parent.data.pendingInsert = queue
} else {
for (let i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i])
}
}
}
let hydrationBailed = false
// list of modules that can skip create hook during hydration because they
// are already rendered on the client or has no need for initialization
// Note: style is excluded because it relies on initial clone for future
// deep updates (#7063).
const isRenderedModule = makeMap('attrs,class,staticClass,staticStyle,key')
// Note: this is a browser-only function so we can assume elms are DOM nodes.
function hydrate (elm, vnode, insertedVnodeQueue, inVPre) {
let i
const { tag, data, children } = vnode
inVPre = inVPre || (data && data.pre)
vnode.elm = elm
if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {
vnode.isAsyncPlaceholder = true
return true
}
// assert node match
if (process.env.NODE_ENV !== 'production') {
if (!assertNodeMatch(elm, vnode, inVPre)) {
return false
}
}
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode, true /* hydrating */)
if (isDef(i = vnode.componentInstance)) {
// child component. it should have hydrated its own tree.
initComponent(vnode, insertedVnodeQueue)
return true
}
}
if (isDef(tag)) {
if (isDef(children)) {
// empty element, allow client to pick up and populate children
if (!elm.hasChildNodes()) {
createChildren(vnode, children, insertedVnodeQueue)
} else {
// v-html and domProps: innerHTML
if (isDef(i = data) && isDef(i = i.domProps) && isDef(i = i.innerHTML)) {
if (i !== elm.innerHTML) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' &&
typeof console !== 'undefined' &&
!hydrationBailed
) {
hydrationBailed = true
console.warn('Parent: ', elm)
console.warn('server innerHTML: ', i)
console.warn('client innerHTML: ', elm.innerHTML)
}
return false
}
} else {
// iterate and compare children lists
let childrenMatch = true
let childNode = elm.firstChild
for (let i = 0; i < children.length; i++) {
if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue, inVPre)) {
childrenMatch = false
break
}
childNode = childNode.nextSibling
}
// if childNode is not null, it means the actual childNodes list is
// longer than the virtual children list.
if (!childrenMatch || childNode) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' &&
typeof console !== 'undefined' &&
!hydrationBailed
) {
hydrationBailed = true
console.warn('Parent: ', elm)
console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children)
}
return false
}
}
}
}
if (isDef(data)) {
let fullInvoke = false
for (const key in data) {
if (!isRenderedModule(key)) {
fullInvoke = true
invokeCreateHooks(vnode, insertedVnodeQueue)
break
}
}
if (!fullInvoke && data['class']) {
// ensure collecting deps for deep class bindings for future updates
traverse(data['class'])
}
}
} else if (elm.data !== vnode.text) {
elm.data = vnode.text
}
return true
}
function assertNodeMatch (node, vnode, inVPre) {
if (isDef(vnode.tag)) {
return vnode.tag.indexOf('vue-component') === 0 || (
!isUnknownElement(vnode, inVPre) &&
vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase())
)
} else {
return node.nodeType === (vnode.isComment ? 8 : 3)
}
}
/** * 这里返回一个patch函数供后续对vnode进行patch操作 * 这里的patch操作是指, 将oldVnode对应的实在DOM更改为vnode对应的实在DOM, 所须要的最低性能开销的操作(或者说是较低) * 参数中的oldVnode是更新前的旧节点, vnode是将要更新的新节点, hydrating是一个flag标识是否与原生DOM混合, removeOnly是在过渡动画中应用 */
return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
// 这里很简略, 如果新节点不存在, 旧节点也不存在, 无需任何操作, 如果新节点不存在,但旧节点存在, 阐明须要删除旧节点, 调用一个销毁钩子
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
isInitialPatch = true
createElm(vnode, insertedVnodeQueue, parentElm, refElm)
} else {
// 走到这里 阐明新旧节点都存在, 这时比较复杂, 分几种状况解决
// 先通过nodeType判断是否是真正的节点, 真正的节点nodeType取值范畴是1~12
// vue里罕用的根本只有三种 1代表是dom元素节点 3是文本节点 8是正文节点
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
// 惯例状况下, 新旧节点是类似节点, 对新旧节点做具体的比照操作
patchVnode(oldVnode, vnode, insertedVnodeQueue, 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
}
// 须要用hydrate函数将虚构DOM和实在DOM进行映射
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(parentElm, [oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
}
能够看到patch
接管的参数
oldVnode
:旧的虚构节点vnode
:新的虚构节点hydrating
:是否映射removeOnly
:标识parentElm
:父节点refElm
:被插入之后的占位符
那么外围diff
代码在于 sameVnode
、createElm
、patchVNode
咱们顺次开展来说
sameVnode
顾名思义能够看判断两个节点是不是同一个节点
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)
)
)
)
}
/** * 节点 key 必须雷同 * tag、正文、data是否存在、input类型是否雷同 * 如果isAsyncPlaceholder是true,则须要asyncFactory属性雷同 */
createElm
// 创立一个节点
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
// 节点曾经被渲染, 须要应用一个克隆节点
if (isDef(vnode.elm) && isDef(ownerArray)) {
// This vnode was used in a previous render!
// now it's used as a new node, overwriting its elm would cause
// potential patch errors down the road when it's used as an insertion
// reference node. Instead, we clone the node on-demand before creating
// associated DOM element for it.
vnode = ownerArray[index] = cloneVNode(vnode)
}
// 创立组件节点 详见本文件中的createComponent办法
vnode.isRootInsert = !nested // for transition enter check
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
/** * 如果要创立的节点有tag属性, 这里做一下校验 * 如果该节点下面有v-pre指令, 间接给flag加1 * 如果没有v-pre须要调用isUnknownElement判断标签是否非法, 而后给出正告 */
if (isDef(tag)) {
if (process.env.NODE_ENV !== 'production') {
if (data && data.pre) {
creatingElmInVPre++
}
if (isUnknownElement(vnode, creatingElmInVPre)) {
warn(
'Unknown custom element: <' + tag + '> - did you ' +
'register the component correctly? For recursive components, ' +
'make sure to provide the "name" option.',
vnode.context
)
}
}
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
/* istanbul ignore if */
if (__WEEX__) {
// in Weex, the default insertion order is parent-first.
// List items can be optimized to use children-first insertion
// with append="tree".
const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
if (!appendAsTree) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
createChildren(vnode, children, insertedVnodeQueue)
if (appendAsTree) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
} else {
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
if (process.env.NODE_ENV !== 'production' && data && data.pre) {
creatingElmInVPre--
}
} 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)
}
}
此段代码就是创立实在dom
的目标,下一章谈判到。
patchVnode
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
// 如果oldVnode跟vnode完全一致,那么不须要做任何事件
if (oldVnode === vnode) {
return
}
const elm = vnode.elm = oldVnode.elm
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
} else {
vnode.isAsyncPlaceholder = true
}
return
}
// 如果oldVnode跟vnode都是动态节点,且具备雷同的key
// 当vnode是克隆节点或是v-once指令管制的节点时
// 只须要把oldVnode.elm和oldVnode.child都复制到vnode上,也不必再有其余操作
// reuse element for static trees.
// note we only do this if the vnode is cloned -
// if the new node is not cloned it means the render functions have been
// reset by the hot-reload-api and we need to do a proper re-render.
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
const oldCh = oldVnode.children
const ch = vnode.children
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
// 如果vnode不是文本节点或正文节点
if (isUndef(vnode.text)) {
// 如果oldVnode和vnode都有子节点,且2方的子节点不完全一致,就执行updateChildren
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) {
// 如果只有vnode有子节点,那就创立这些子节点
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
// 如果只有oldVnode有子节点,那就把这些节点都删除
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
// 如果oldVnode和vnode都没有子节点,然而oldVnode是文本节点或正文节点,就把vnode.elm的文本设置为空字符串
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, '')
}
// 如果vnode是文本节点或正文节点,然而vnode.text != oldVnode.text时,只须要更新vnode.elm的文本内容即可
} else if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm, vnode.text)
}
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}
具体代码性能曾经解释的很分明了,这里的addVnodes
和removeVnodes
就是新增与移除虚构节点,外围代码咱们次要关注一个updateChildren
updateChildren
// diff操作外围算法
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
// 记录新旧节点列表的首尾元素 用于比拟
let oldStartIdx = 0 // 旧列表终点地位
let newStartIdx = 0 // 新列表终点地位
let oldEndIdx = oldCh.length - 1 // 旧列表起点地位
let oldStartVnode = oldCh[0] // 旧列表终点值
let oldEndVnode = oldCh[oldEndIdx] // 旧列表起点值
let newEndIdx = newCh.length - 1 // 新列表起点地位
let newStartVnode = newCh[0] // 新列表终点值
let newEndVnode = newCh[newEndIdx] // 新列表起点值
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
// removeOnly is a special flag used only by <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
// 在transition中 不能挪动节点
const canMove = !removeOnly
// 查看是否有反复的key
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(newCh)
}
// 一共分四种状况探讨, 旧列表第一个与新列表第一个比照, 旧列表最初一个与新列表最初一个比照
// 而后新列表第一个和旧列表最初一个比照, 新列表最初一个和旧列表第一个比照
// 之所以要穿插头尾比照, 是为了避免最差的状况呈现
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
// 以上四种状况都不满足时, 应用新列表第一个vdom的key去旧列表查找
// 如果能够找到key雷同的元素, 间接进行patch而后进入下一次循环
// 找不到则插入一个新节点
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
// 新旧列表其中之一全副循环实现后, 开始清理残余的节点
// 如果旧列表全副遍历实现, 新列表还有残余, 间接创立这些新节点
// 反之, 如果新列表全副遍历, 旧列表还有残余, 间接删除这些旧节点
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
这里利用了while
循环与双指针比照
新旧虚构dom
定义指针变量
let oldStartIdx = 0 // 旧列表终点地位
let newStartIdx = 0 // 新列表终点地位
let oldEndIdx = oldCh.length - 1 // 旧列表起点地位
let oldStartVnode = oldCh[0] // 旧列表终点值
let oldEndVnode = oldCh[oldEndIdx] // 旧列表起点值
let newEndIdx = newCh.length - 1 // 新列表起点地位
let newStartVnode = newCh[0] // 新列表终点值
let newEndVnode = newCh[newEndIdx] // 新列表起点值
定义循环
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
// 以上四种状况都不满足时, 应用新列表第一个vdom的key去旧列表查找
// 如果能够找到key雷同的元素, 间接进行patch而后进入下一次循环
// 找不到则插入一个新节点
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
检测oldStartVnode、oldEndVnode
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx]
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
}
如果oldStartVnode
不存在,oldCh
起始点向后挪动。如果oldEndVnode
不存在,oldCh
终止点向前挪动。
oldStartVnode 和 newStartVnode 是雷同节点
else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
}
如果oldStartVnode
和 newStartVnode
是雷同节点,则patchVnode
,同时彼此向后挪动一位
oldEndVnode 和 newEndVnode 是雷同节点
else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
}
如果oldEndVnode
和 newEndVnode
是雷同节点,则patchVnode
,同时彼此向前挪动一位
oldStartVnode 和 newEndVnode 是雷同节点
else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
}
如果oldStartVnode
和 newEndVnode
是雷同节点,则先 patchVnode
,而后把oldStartVnode
移到oldCh
最初的地位即可,而后oldStartIdx
向后挪动一位,newEndIdx
向前挪动一位
oldEndVnode 和 newStartVnode 是雷同节点
else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
}
如果oldEndVnode
和 newStartVnode
是雷同节点,则先 patchVnode
,而后把oldEndVnode
移到oldCh
最前的地位即可,而后newStartIdx
向后挪动一位,oldEndIdx
向前挪动一位
key不雷同执行createElm办法
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
}
如果以上条件都不匹配,则查找oldVnode
中与vnode
具备雷同key
的节点,并将查找的后果赋值给elmToMove
。如果找不到雷同key
的节点,则示意是新创建的节点
key雷同,就认为是同一节点
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
}
newStartVnode = newCh[++newStartIdx]
若为同一类型就调用patchVnode
,就将对应下标处的oldVnode
设置为undefined
,把vnodeToMove
插入到oldCh
之前,newStartIdx
持续向后挪动。如果两个 vnode
不雷同,视为新元素,执行 createElm
创立。
如果老dom的开始索引大于完结索引,新dom数组大于老dom数组,示意新增会调用addVnodes办法
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
}
如果老dom的开始索引小于完结索引,新dom数组小于老dom数组,示意新增会调用removeVnodes办法
else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
总结
因为古代diff算法策略
是同层级比拟,广度优先
,故而古代算法复杂度为O(n)
这一章咱们讲述了传统diff算法复杂度
,O(n^3)
到古代的O(n)
的实现的一个思路,下一章就开始解说比照过后的vdom
如何映射
成实在dom
发表回复