前言vue-next对虚拟dom的patch更新做了一系列的优化,增加blockfromcompilation减少vdom之间的比较次数,提升操作减少内存开销。为自己写这篇文章,把知识点记录下来。如有错误,请不吝赐教。VDOMVDOM的概念简单来说就是用js对象来模拟真实的DOM树。由于MV**的架构,真实的DOM树应该随着数据(Vue2.x中的数据)的变化而变化。这些变化可能在以下几个方面:v-ifv-fordynamicprops(如:class,Vue框架要做的其实很简单:当用户改变数据时,正确更新DOM树,也就是核心的VDOMpatch和diff算法,在Vue2.x中的实践在Vue2.x中,当数据发生变化时,需要对所有节点进行patch和diff操作。比如下面的DOM结构:
会在节点第一次挂载时生成真实的DOM,ifmutableItems.push({key:'asdf',desc:'一个新的liitem'})预期结果是页面出现一个新的li元素,content是一个新的li项,在Vue2.x中,通过patchchildren传递ul元素对应的vnode进行diff操作。具体操作这里不讨论,但是这个operation需要比较li对应的所有vnode。缺点是2.x版本的diff操作需要遍历所有元素。本例中包含了span和第一个li元素,但这两个元素是静态的,不需要比较。无论数据如何变化,静态元素都不会再发生变化。Vue-next在编译时优化了这个操作,即Block。分块进入上述模板,vue-next中生成的渲染函数为:const_Vue=Vueconst{createVNode:_createVNode}=_Vueconst_hoisted_1=_createVNode("span",{class:"header"},"I'mheader",-1/*HOISTED*/)const_hoisted_2=_createVNode("li",null,"第一个静态li",-1/*HOISTED*/)returnfunctionrender(_ctx,_cache){with(_ctx){const{createVNode:_createVNode,渲染列表:_renderList,片段:_Fragment,openBlock:_openBlock,createBlock:_createBlock,toDisplayString:_toDisplayString}=_Vuereturn(_openBlock(),_createBlock(_Fragment,null,[_hoisted_1,_createVNodeull("ul",n[_hoisted_2,(_openBlock(true),_createBlock(_Fragment,null,_renderList(state.mutableItems,(item)=>{return(_openBlock(),_createBlock("li",{key:item.key},_toDisplayString(item.desc),1/*TEXT*/))}),128/*KEYED_FRAGMENT*/))])],64/*STABLE_FRAGMENT*/))}}我们可以看到调用了openBlock和createBlock方法,这两个代码小鬼这种方法的实现也很简单:constblockStack:(VNode[]|null)[]=[]letcurrentBlock:VNode[]|null=nullletshouldTrack=1//openBlockexport函数openBlock(disableTracking=false){blockStack.push((currentBlock=disableTracking?null:[]))}exportfunctioncreateBlock(type:VNodeTypes|ClassComponent,props?:{[key:string]:any}|null,children?:any,patchFlag?:number,dynamicProps?:string[]):VNode{//避免使用patchFlag跟踪自身的块shouldTrack--constvnode=createVNode(type,props,children,patchFlag,dynamicProps)shouldTrack++//将当前区块子节点保存在区块vnodevnode.dynamicChildren=currentBlock||EMPTY_ARR//关闭区块blockStack.pop()currentBlock=blockStack[blockStack.length-1]||null//一个块总是会被修补,所以将它作为它的子块来跟踪//父块if(currentBlock){currentBlock.push(vnode)}returnvnode}更详细的注解还请看源码中的注解,写的十分详细,便于理解这里openBlock是初始化一个block,createBlock是为当前编译的内容生成一个block。这里的代码行:vnode.dynamicChildren=currentBlock||函数://createVNodefunction_createVNode(type:VNodeTypes|ClassComponent,props:(Data&VNodeProps)|null=null,children:unknown=null,patchFlag:number=0,dynamicProps:string[]|null=null){/***一系列代码**///补丁标志的存在表示该节点需要在更新时打补丁。//组件节点也应该始终打补丁,因为即使//组件不需要更新,它也需要将实例持久化到//下一个vnode,以便稍后可以正确卸载它。if(shouldTrack>0&¤tBlock&&//EVENTS标志仅用于水合,如果它是唯一的标志,//由于处理程序缓存,vnode不应被视为动态。patchFlag!==PatchFlags.HYDRATE_EVENTS&&(patchFlag>0||形状F滞后&ShapeFlags.SUSPENSE||shapeFlag&ShapeFlags.STATEFUL_COMPONENT||shapeFlag&ShapeFlags.FUNCTIONAL_COMPONENT)){currentBlock.push(vnode)}}上面的函数是在模板编译成ast生成VNode后调用的,所以有patchFlagFlag,如果是动态节点,Block开启在这次会将节点塞进Block中,这样createBlock返回的VNode中就会有dynamicChildren至此,通过本文的案例,经过模板编译和render函数运行优化,生成了如下结构的vnode:constresult={type:Symbol(Fragment),patchFlag:64,children:[{type:'span',patchFlag:-1,...},{type:'ul',patchFlag:0,children:[{type:'li',patchFlag:-1,...},{type:Symbol(片段),孩子们:[{类型:'li',patchFlag:1...},{类型:'li',patchFlag:1...}]}]}],dynamicChildren:[{类型:Symbol(片段),patchFlag:128,children:[{type:'li',patchFlag:1...},{type:'li',patchFlag:1...}]}]}上面的结果是不完整的,但是我们会暂时只关心这些属性。可以看到result.children的第一个元素是span,patchFlag=-1,result有一个dynamicChildren数组,里面只有两个动态lis。如果后面更改数据,新的vnode.dynamicChildren将具有前三个li元素。patchpatch部分其实区别不大,就是根据vnode的类型进行不同的patch操作:functionpatchElement(n1,n2){let{dynamicChildren}=n2//一系列的操作n1.dynamicChildren!,dynamicChildren,el,parentComponent,parentSuspense,areChildrenSVG)}elseif(!optimized){//fulldiffpatchChildren(n1,n2,el,null,parentComponent,parentSuspense,areChildrenSVG)}}可以看到,if有dynamicChildren然后是vue2。x版本中的diff操作被patchBlockChildren()代替,参数只有dynamicChildren,即不进行静态diff操作,如果vue-next的patch中没有dynamicChildren,则进行一次完整的diff操作执行,写在注释中完整差异的后续代码。在本文的最后,我并没有深入解释代码的实现层次。一是因为自己没有力气,还在看源码。另外一个就是我个人认为阅读源码不应该是死路一条。以大局为重,再慢慢映射。先了解各个部分的作用,再进行介绍。如果想着阅读源码,应该会有更多的收获。大家互相鼓励。