本篇文章给大家分享了关于vue的diff算法的相关知识点总结,有兴趣的朋友参考学习下。
虚拟dom
diff算法首先要明确一个概念就是diff的对象是虚拟dom,更新真实dom则是diff算法的结果
vnode基类
constructor (
。。。
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fncontext = undefined
this.fnoptions = undefined
this.fnscopeid = undefined
this.key = data && data.key
this.componentoptions = componentoptions
this.componentinstance = undefined
this.parent = undefined
this.raw = false
this.isstatic = false
this.isrootinsert = true
this.iscomment = false
this.iscloned = false
this.isonce = false
this.asyncfactory = asyncfactory
this.asyncmeta = undefined
this.isasyncplaceholder = false
}
这个部分的代码 主要是为了更好地知道在diff算法中具体diff的属性的含义,当然也可以更好地了解vnode实例
整体过程
核心函数是patch函数
isundef判断(是不是undefined或者null)
// empty mount (likely as component), create new root elementcreateelm(vnode, insertedvnodequeue) 这里可以发现创建节点不是一个一个插入,而是放入一个队列中统一批处理
核心函数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的比较(注意这里的data指的是vnodedata),input的话直接比较type。
export interface vnodedata {
key?: string | number;
slot?: string;
scopedslots?: { [key: string]: scopedslot };
ref?: string;
tag?: string;
staticclass?: string;
class?: any;
staticstyle?: { [key: string]: any };
style?: object[] | object;
props?: { [key: string]: any };
attrs?: { [key: string]: any };
domprops?: { [key: string]: any };
hook?: { [key: string]: function };
on?: { [key: string]: function | function[] };
nativeon?: { [key: string]: function | function[] };
transition?: object;
show?: boolean;
inlinetemplate?: {
render: function;
staticrenderfns: function[];
};
directives?: vnodedirective[];
keepalive?: boolean;
}
这会确认两个节点是否有进一步比较的价值,不然直接替换
替换的过程主要是一个createelm函数 另外则是销毁oldvnode
// destroy old node
if (isdef(parentelm)) {
removevnodes(parentelm, [oldvnode], 0, 0)
} else if (isdef(oldvnode.tag)) {
invokedestroyhook(oldvnode)
}
插入过程简化来说就是判断node的type分别调用
createcomponent(会判断是否有children然后递归调用)
createcomment
createtextnode
创建后使用insert函数
之后需要用hydrate函数将虚拟dom和真是dom进行映射
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 patchvnode (oldvnode, vnode, insertedvnodequeue, removeonly) {
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
}
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)
}
if (isundef(vnode.text)) {
if (isdef(oldch) && isdef(ch)) {
if (oldch !== ch) updatechildren(elm, oldch, ch, insertedvnodequeue, removeonly)
} else if (isdef(ch)) {
if (isdef(oldvnode.text)) nodeops.settextcontent(elm, '')
addvnodes(elm, null, ch, 0, ch.length - 1, insertedvnodequeue)
} else if (isdef(oldch)) {
removevnodes(elm, oldch, 0, oldch.length - 1)
} else if (isdef(oldvnode.text)) {
nodeops.settextcontent(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)
}
}
const el = vnode.el = oldvnode.el 这是很重要的一步,让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化。
比较二者引用是否一致
之后asyncfactory不知道是做什么的,所以这个比较看不懂
静态节点比较key,相同后也不做重新渲染,直接拷贝componentinstance(once命令在此生效)
如果vnode是文本节点或注释节点,但是vnode.text != oldvnode.text时,只需要更新vnode.elm的文本内容就可以
children的比较
如果只有oldvnode有子节点,那就把这些节点都删除
如果只有vnode有子节点,那就创建这些子节点,这里如果oldvnode是个文本节点就把vnode.elm的文本设置为空字符串
都有则updatechildren,这个之后详述
如果oldvnode和vnode都没有子节点,但是oldvnode是文本节点或注释节点,就把vnode.elm的文本设置为空字符串
updatechildren
这部分重点还是关注整个算法
首先四个指针,oldstart,oldend,newstart,newend,两个数组,oldvnode,vnode。
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
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 {
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)
}
}
一个循环比较的几种情况和处理(以下的++ --均指index的++ --)比较则是比较的node节点,简略写法 不严谨 比较用的是samevnode函数也不是真的全等
整体循环不结束的条件oldstartidx <= oldendidx && newstartidx <= newendidx
oldstart === newstart,oldstart++ newstart++
oldend === newend,oldend-- newend--
oldstart === newend, oldstart插到队伍末尾 oldstart++ newend--
oldend === newstart, oldend插到队伍开头 oldend-- newstart++
剩下的所有情况都走这个处理简单的说也就两种处理,处理后newstart++
newstart在old中发现一样的那么将这个移动到oldstart前
没有发现一样的那么创建一个放到oldstart之前
循环结束后并没有完成
还有一段判断才算完
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)
}
简单的说就是循环结束后,看四个指针中间的内容,old数组中和new数组中,多退少补而已
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
angular5给组件本身的标签添加样式class的方法
vue-cli开发环境实现跨域请求的方法
详解vue-cli webpack移动端自动化构建rem问题
以上就是vue的diff算法知识点总结的详细内容。