是因为Vue3.0出来这么久了。还是想写这篇文章,因为我还在看Vue2.x的源码,感觉豁然开朗。作为一个刚毕业的新人,突然对vue框架的整体设计和架构有了一点了解,于是突然无头无尾的写下了diff算法。在我看来,Vue的核心内容分为以下几个部分:生成渲染函数(运行时版本没有此功能,用户需要向渲染函数传递参数)生成VNode将VNode渲染成真实的DOM监控数据变化生成newVNodeOldandnewVNode对比和更新页面大致分为以上几点(可能我没有看过完整的代码,如有遗漏还望大家不吝赐教)。其实大家可以选择自己感兴趣的点来查看源码学习。源码我也看了大部分,对第六点打补丁的过程做个记录。准备工作我们使用vue-cli把2.x模板拉下来,简单写下如下代码://main.jsnewVue({el:'#app',data(){return{list:[1,2,3,4]}},render(h){returnh('ul',{class:'c-ul'},this.list.map(_=>h('li',{key:_},_)))},mounted(){this.list=[2,5,3,6,7,1]}})手动写render函数的原因是不想设计编译器,而render给开发者可以直接写VNode的能力。上面的demo会进行列表渲染,并在开头按照[1,2,3,4]的顺序记录oldVNode。当挂载的生命周期内列表发生变化时,此时会重新生成newVNode,然后对新旧VNode进行Patch。本文主要讲这个操作。VNode,你可以运行demo工程,然后在控制台打断点,看看VNode的数据结构是什么样的。这里我直接上传图片。上图是第一次渲染的oldVNode。让我们简化VNode数据结构如下://oldVNodeletoldVNode={tag:'ul',data:{class:'c-ul'},children:[{tag:'li',data:{key:1},children:[{tag:undefined,text:1}]},{tag:'li',data:{key:2},children:[{tag:undefined,text:2}]},//以上structure有4个VNodetaggedwithliaschildren]}同样的操作也会在mounted生命周期调用后生成一个VNode,结构和上面完全一样,只是newVNode.children的内容改变了:letoldVNode={tag:'ul',data:{class:'c-ul'},children:[{tag:'li',data:{key:2},children:[{tag:undefined,text:2}]},{tag:'li',data:{key:5},children:[{tag:undefined,text:5}]},//tag为li的VNode有6个,与children结构相同]}然后用这两个VNode对象,我们可以比较一下注意oldVNode中的每个VNode对象都有对真实dom节点的引用,如:oldVNode.elmpatchVNode实际上,在进入patchVNode之前,Vue会在两个VNode上调用sameVNode方法来判断是否需要打补丁。sameVNode的逻辑很简单就是key相同,tag相同,是否与注释节点相同(不),数据对象是否同时定义(不),判断输入。我们这里引用的例子,显然是返回了true,具体是同一个VNode逻辑读者自己查。//patchVNode()functionpatchVnode(oldVNode,newVNode,insertedVnodeQueue,ownerArray,index,removeOnly){constelm=newVNode.elm=oldVNode.elm//省略if(isUndef(newVNode.text)){if(isDef(newVNode.children)&&isDef(oldVNode.children)){//如果新旧VNode都有孩子updateChildren(elm,oldVNode.children,newVNode.children)//比较孩子}elseif(isDef(newVNode.children)){//ifnewVnode有子节点,oldVNode没有//添加节点if(isDef(oldVNode.text)){//如果oldVNode有文本,其子节点本来就是文本,需要清空文本setTextContent(elm,'')}addVNodes()//将newVNode的Children添加到dom元素中}elseif(isDef(oldVNode.children)){//如果oldVNode有children而newVNode没有removeVNodes()//删除原来的Children}}elseif(oldVNode.text!==newVNode.text){setTextContent(elm,newVNode.text)}}让newVNode.elm指向入口方法开头的oldVNode.elm。之所以做这个赋值是因为进入patchVNode的前两个VNode已经被sameVNode判断过了。它们是具有相同标签(相同是ul)的VNode。所以这里可以直接让newVNode.elm重用oldVNode.elm,然后判断newVNode.text是否存在。如果存在,说明其子节点只有文本内容,如果oldVNode的文本内容(可能为空或非空)与oldVNode.text不同,则直接替换textContent即可完成patch过程。另一个分支是判断newVNode和oldVNode的children。如果单边有孩子(即一个没有一个),那么要么删除该节点,要么添加该节点。如果有孩子,则需要updateChildren。updateChildren部分其实就是diff的真正过程。因为这涉及到两个VNode数组的比较。在理解diff算法之前,我们先考虑以下问题:updateChildren的目的是将newChildren数组中的VNode渲染成真正的dom元素,然后根据oldChildren数组中的VNode替换原来渲染的dom元素。那是最简单的方法是什么毫无疑问,最简单的方法是先删除,然后添加:functionupdateChildren(parentElm,oldCh,newCh){//先删除所有原始元素for(leti=0,l=oldCh.length;i
