当学习成为习惯,知识就成为常识。感谢您的关注、点赞、收藏和评论。有新视频和文章会第一时间发到微信公众号。欢迎关注:李永宁lyn的文章已收录到github仓库liyongning/blog。欢迎收看星空。前言上篇手写Vue2系列初始渲染中的原始标签、自定义组件、槽的初始渲染,当然也涉及到v-bind、v-model、v-on指令的原理。第一次渲染完成后,就是后续更新的时间了:响应式数据更新->setter拦截更新操作->dep通知watcher执行update方法->然后执行updateComponent方法更新组件->执行render生成一个新的vnode->将vnode传递给vm._update方法->调用patch方法->执行patchVnode进行DOMdiff操作->完成更新目标那么,本文的目标就是实现DOMdiff并完成后续更新。涉及的知识点只有一个:DOMdiff。接下来就是实现DOMdiff,完成响应式数据的后续更新。patch/src/compiler/patch.js/***负责组件的首次渲染和后续更新*@param{VNode}oldVnodeoldVNode*@param{VNode}vnodenewVNode*/exportdefaultfunctionpatch(oldVnode,vnode){if(oldVnode&&!vnode){//如果旧节点存在,新节点不存在,销毁组件返回}if(!oldVnode){//oldVnode不存在,说明子组件渲染完成第一次}else{if(oldVnode.nodeType){//真正的节点,表示根组件是第一次渲染}else{//后续更新patchVnode(oldVnode,vnode)}}}patchVnode/src/compiler/patch.js/***比较新旧节点,找出不同点,然后更新旧节点*@param{*}oldVnode旧节点的vnode*@param{*}vnode新节点的vnode*/functionpatchVnode(oldVnode,vnode){//如果新旧节点相同,则直接结束if(oldVnode===vnode)return//在旧v上同步真实节点node到新的vnode,否则,在后续更新vnode.elm=oldVnode期间,vnode.elm将为空。elm//来这里说明新老节点不一样,然后获取他们的子节点,比较子节点constch=vnode.childrenconstoldCh=oldVnode.childrenif(!vnode.text){//新节点没有文本节点if(ch&&oldCh){//表示旧节点和新节点有子节点//diffupdateChildren(ch,oldCh)}elseif(ch){//旧节点没有子节点,新节点有子节点//添加子节点}else{//新节点没有子节点,旧节点有子节点//删除这些子节点}}else{//新节点有文本节点if(vnode.text.expression){//表示表达式的存在//获取表达式的新值constvalue=JSON.stringify(vnode.context[vnode.text.expression])//旧值try{constoldValue=oldVnode.elm.textContentif(value!==oldValue){//如果新旧值不同,更新oldVnode.elm.textContent=value}}catch{//防止更新时遇到slot,导致报错//目前没有处理slot数据的响应式更新}}}}updateChildren/src/compiler/patch.js/***diff,比较子节点,找出差异,然后将差异更新到旧的nodes*@param{*}ch新vnode的所有子节点Node*@param{*}oldCh旧vnode的所有子节点*/functionupdateChildren(ch,oldCh){//四个游标//开始索引新的子节点,称为newstartletnewStartIdx=0//newendletnewEndIdx=ch.length-1//oldstartletoldStartIdx=0//oldendletoldEndIdx=oldCh.length-1//loopthroughwhile(newStartIdx<=newEndIdx&&oldStartIdx<=oldEndIdx){//根源是web中的DOM操作特性,做了四个假设。Inter-complexity//新开始节点constnewStartNode=ch[newStartIdx]//新结束节点constnewEndNode=ch[newEndIdx]//旧开始节点constoldStartNode=oldCh[oldStartIdx]//旧结束节点constoldEndNode=oldCh[oldEndIdx]]if(sameVNode(newStartNode,oldStartNode)){//假设新起点和旧起点是同一个节点//比较两个节点,找出不同点并更新patchVnode(oldStartNode,newStartNode)//移动光标oldStartIdx++newStartIdx++}elseif(sameVNode(newStartNode,oldEndNode)){//假设新起点和旧终点是同一个节点patchVnode(oldEndNode,newStartNode)//将旧终点移动到新起点oldEndNode.elm.parentNode.insertBefore(oldEndNode.elm,oldCh[newStartIdx].elm)//移动光标newStartIdx++oldEndIdx--}elseif(sameVNode(newEndNode,oldStartNode)){//假设新的结束点和旧的开始点是同一个节点patchVnode(oldStartNode,newEndNode)//移除旧的Start移动到新的结束位置oldStartNode.elm.parentNode.insertBefore(oldStartNode.elm,oldCh[newEndIdx].elm.nextSibling)//移动光标newEndIdx--oldStartIdx++}elseif(sameVNode(newEndNode,oldEndNode)){//假设新端和旧端是同一个节点patchVnode(oldEndNode,newEndNode)//移动光标newEndIdx--oldEndIdx--}else{//两者都不是以上假设命中,然后老老实实遍历,找到相同的元素}}//跳出循环,说明有一个节点先遍历过if(newStartIdx
