您好,欢迎访问一九零五行业门户网

在vue中实现虚拟dom的patch(详细教程)

这篇文章主要介绍了vue 虚拟dom的patch源码分析,现在分享给大家,也给大家做个参考。
本文介绍了vue 虚拟dom的patch源码分析,分享给大家,具体如下:
源码目录:src/core/vdom/patch.js
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 const canmove = !removeonly while (oldstartidx <= oldendidx && newstartidx <= newendidx) { // 开始索引大于结束索引,进不了 if (isundef(oldstartvnode)) { oldstartvnode = oldch[++oldstartidx] // vnode已经被移走了。 } else if (isundef(oldendvnode)) { oldendvnode = oldch[--oldendidx] } else if (samevnode(oldstartvnode, newstartvnode)) { patchvnode(oldstartvnode, newstartvnode, insertedvnodequeue) oldstartvnode = oldch[++oldstartidx] // 索引加1。是去对比下一个节点。比如之前start=a[0],那现在start=a[1],改变start的值后再去对比start这个vnode newstartvnode = newch[++newstartidx] } else if (samevnode(oldendvnode, newendvnode)) { patchvnode(oldendvnode, newendvnode, insertedvnodequeue) oldendvnode = oldch[--oldendidx] newendvnode = newch[--newendidx] } else if (samevnode(oldstartvnode, newendvnode)) { patchvnode(oldstartvnode, newendvnode, insertedvnodequeue) canmove && nodeops.insertbefore(parentelm, oldstartvnode.elm, nodeops.nextsibling(oldendvnode.elm))// 把节点b移到树的最右边 oldstartvnode = oldch[++oldstartidx] newendvnode = newch[--newendidx] } else if (samevnode(oldendvnode, newstartvnode)) { old.end.d=new.start.d patchvnode(oldendvnode, newstartvnode, insertedvnodequeue) canmove && nodeops.insertbefore(parentelm, oldendvnode.elm, oldstartvnode.elm)// vnode moved left,把d移到c的左边。=old.start->old.end 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)) { createelm(newstartvnode, insertedvnodequeue, parentelm, oldstartvnode.elm) // 创建新节点,后面执行了nodeops.insertbefore(parent, elm, ref) } else { vnodetomove = oldch[idxinold] /* istanbul ignore if */ if (process.env.node_env !== 'production' && !vnodetomove) { warn( 'it seems there are duplicate keys that is causing an update error. ' + 'make sure each v-for item has a unique key.' ) } 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] } } 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) // 删除旧的c,removenode(ch.elm) } }
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) ) ) ) } /** * 比较新旧vnode节点,根据不同的状态对dom做合理的更新操作(添加,移动,删除)整个过程还会依次调用prepatch,update,postpatch等钩子函数,在编译阶段生成的一些静态子树,在这个过程 * @param oldvnode 中由于不会改变而直接跳过比对,动态子树在比较过程中比较核心的部分就是当新旧vnode同时存在children,通过updatechildren方法对子节点做更新, * @param vnode * @param insertedvnodequeue * @param removeonly */ 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 } // 用于静态树的重用元素。 // 注意,如果vnode是克隆的,我们只做这个。 // 如果新节点不是克隆的,则表示呈现函数。 // 由热重加载api重新设置,我们需要进行适当的重新渲染。 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) } } function insertbefore (parentnode, newnode, referencenode) { parentnode.insertbefore(newnode, referencenode); } /** * * @param vnode根据vnode的数据结构创建真实的dom节点,如果vnode有children则会遍历这些子节点,递归调用createelm方法, * @param insertedvnodequeue记录子节点创建顺序的队列,每创建一个dom元素就会往队列中插入当前的vnode,当整个vnode对象全部转换成为真实的dom 树时,会依次调用这个队列中vnode hook的insert方法 * @param parentelm * @param refelm * @param nested */ let inpre = 0 function createelm (vnode, insertedvnodequeue, parentelm, refelm, nested) { vnode.isrootinsert = !nested // 过渡进入检查 if (createcomponent(vnode, insertedvnodequeue, parentelm, refelm)) { return } const data = vnode.data const children = vnode.children const tag = vnode.tag if (isdef(tag)) { if (process.env.node_env !== 'production') { if (data && data.pre) { inpre++ } if ( !inpre && !vnode.ns && !( config.ignoredelements.length && config.ignoredelements.some(ignore => { return isregexp(ignore) ? ignore.test(tag) : ignore === tag }) ) && config.isunknownelement(tag) ) { 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) { inpre-- } } 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 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 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) } } } }
updatechildren方法主要通过while循环去对比2棵树的子节点来更新dom,通过对比新的来改变旧的,以达到新旧统一的目的。
通过一个例子来模拟一下:
假设有新旧2棵树,树中的子节点分别为a,b,c,d等表示,不同的代号代表不同的vnode,如:
在设置好状态后,我们开始第一遍比较,此时oldstartvnode=a,newstartvnode=a;命中了samevnode(oldstartvnode,newstartvnode)逻辑,则直接调用patchvnode(oldstartvnode,newstartvnode,insertedvnodequeue)方法更新节点a,接着把oldstartidx和newstartidx索引分别+1,如图:
更新完节点a后,我们开始第2遍比较,此时oldstartvnode=b,newendvnode=b;命中了samevnode(oldstartvnode,newendvnode)逻辑,则调用patchvnode(oldstartvnode, newendvnode, insertedvnodequeue)方法更新节点b,接着调用canmove && nodeops.insertbefore(parentelm, oldstartvnode.elm, nodeops.nextsibling(oldendvnode.elm)),把节点b移到树的最右边,最后把oldstartidx索引+1,newendidx索引-1,如图:
更新完节点b后,我们开始第三遍比较,此时oldendvnode=d,newstartvnode=d;命中了samevnode(oldendvnode, newstartvnode)逻辑,则调用patchvnode(oldendvnode, newstartvnode, insertedvnodequeue)方法更新节点d,接着调用canmove && nodeops.insertbefore(parentelm, oldendvnode.elm, oldstartvnode.elm),把d移到c的左边。最后把oldendidx索引-1,newstartidx索引+1,如图:
更新完d后,我们开始第4遍比较,此时newstartvnode=e,节点e在旧树里是没有的,因此应该被作为一个新的元素插入,调用createelm(newstartvnode, insertedvnodequeue, parentelm, oldstartvnode.elm),后面执行了nodeops.insertbefore(parent, elm, ref)方法把e插入到c之前,接着把newstartidx索引+1,如图:
插入节点e后,我们可以看到newstartidx已经大于newendidx了,while循环已经完毕。接着调用removevnodes(parentelm, oldch, oldstartidx, oldendidx) 删除旧的c,最终如图:
updatechildren通过以上几步操作完成了旧树子节点的更新,实际上只用了比较小的dom操作,在性能上有所提升,并且当子节点越复杂,这种提升效果越明显。vnode通过patch方法生成dom后,会调用mounted hook,至此,整个vue实例就创建完成了,当这个vue实例的watcher观察到数据变化时,会两次调用render方法生成新的vnode,接着调用patch方法对比新旧vnode来更新dom.
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
jquery选中select组件被选中的值方法
vue.js中$set与数组更新方法_vue.js
vue与vue-i18n结合实现后台数据的多语言切换方法
以上就是在vue中实现虚拟dom的patch(详细教程)的详细内容。
其它类似信息

推荐信息