本篇我们通过学习Vue模板编译原理(二)——AST生成Render字符串来分析Vue源码。预计以后会围绕Vue源码整理一些文章,如下。一起学习Vue双向绑定原理-数据劫持和发布订阅一起学习Vue模板编译原理(一)-Template生成AST一起学习Vue模板编译原理(2)-AST生成Renderstring[一起学Vue虚拟DOM分析——虚拟Dom实现与Dom-diff算法]()这些文章放在我的git仓库:https://github.com/yzsunlei/javascript-series-code-analyzing。觉得有用记得star收藏哦。编译过程模板编译是Vue.js的核心部分。Vue编译原理的整体逻辑主要分为三部分,或者可以说分为三步。上下文如下:Step1:ConverttemplatestringsintoelementASTs(parser)静态节点标记,主要用于虚拟DOM渲染优化(optimizer)Step3:使用elementASTs生成渲染函数代码串(codegenerator)对应Vue源码如下,源码位置在src/compiler/index.htmljsexportconstcreateCompiler=createCompilerCreator(functionbaseCompile(template:string,options:CompilerOptions):CompiledResult{//1.parse,将模板字符串转换为抽象语法树(AST)constast=parse(template.trim(),options)//2.optimize,markstaticnodesonASTif(options.optimize!==false){optimize(ast,options)}//3.generate,抽象语法树(AST)生成renderfunctioncodestringconstcode=generate(ast,options)return{ast,render:code.render,staticRenderFns:code.staticRenderFns}})这篇文档主要讲第三步使用元素AST生成render函数代码串。相应的源代码实现通常称为代码生成器。代码生成器的运行过程在分析代码生成器的原理之前,我们先通过一个例子来看看代码生成器的具体功能。
在上一节“模板生成AST”中我们已经说过,上面的模板会被解析器,解析结果如下:{tag:"div"type:1,staticRoot:false,static:false,plain:true,parent:undefined,attrsList:[],attrsMap:{},children:[{标签:“p”类型:1,staticRoot:假,静态:假,平原:真,父:{标签:“div”,...},attrsList:[],attrsMap:{},孩子:[{type:2,text:"{{name}}",static:false,expression:"_s(name)"}]}]}本节我们将把AST转换成可以直接执行的JavaScript字符串,以及最终结果如下:with(this){return_c('div',[_c('p',[_v(_s(name))]),_v(""),_m(0)])}如果你现在不明白也没关系。其实生成器的过程就是generate函数拿到解析后的AST对象,递归AST树,为不同的AST节点创建不同的内部调用方法,然后组合成可执行的JavaScript字符串,等待后续调用。内部调用方法如果我们查看上面例子生成的JavaScript字符串,会发现里面会有_v、_c、_s之类的东西。这些其实就是Vue内部定义的一些调用方法。_c函数定义在src/core/instance/render.js中。vm.$slots=resolveSlots(options._renderChildren,renderContext)vm.$scopedSlots=emptyObject//定义的_c函数用于创建元素vm._c=(a,b,c,d)=>createElement(vm,a,b,c,d,false)vm.$createElement=(a,b,c,d)=>createElement(vm,a,b,c,d,true)等_s,_v定义在src/core/instance/render-helpers/index.js:exportfunctioninstallRenderHelpers(target:any){target._o=markOncetarget._n=toNumbertarget._s=toStringtarget._l=renderList//生成列表VNodetarget._t=renderSlot//生成解析槽节点target._q=looseEqualtarget._i=looseIndexOftarget._m=renderStatic//生成静态元素target._f=resolveFiltertarget._k=checkKeyCodestarget._b=bindObjectProps//绑定对象属性target._v=createTextVNode//创建文本VNodetarget._e=createEmptyVNode//创建空节点VNodetarget._u=resolveScopedSlotstarget._g=bindObjectListenerstarget._d=bindDynamicKeystarget._p=prependModifier}以上都会生成JavaScript字符串中使用,比如比较常用的_c执行createElement创建VNode,_l对应renderList渲染列表;_v对应createTextVNode创建一个文本VNode;_ecreateEmptyVNode创建一个空的VNode。整个逻辑代码生成器的入口函数是generate。具体定义如下。源码位置在src/compiler/codegen/index.jsexportfunctiongenerate(ast:ASTElement|void,options:CompilerOptions):CodegenResult{//初始化一些选项conststate=newCodegenState(options)//传入ast和生成constcode=ast的选项?genElement(ast,state):'_c("div")'//Keyreturn{//最外层包裹了一个with(this)然后返回render:`with(this){return${code}}`,//这个数组中的函数和VDOM算法优化相关的diff//那些标记为staticRoot节点的VNode会单独生成staticRenderFnsstaticRenderFns:state.staticRenderFns}}generator的入口函数比较简单,先初始化一些配置选项,然后传入ast生成,如果没有ast,直接生成一个空的div,最后返回生成的javascript字符串和一个静态节点的render函数。这里会区分静态节点,方便后面的Vnodediff,即Vue比较算法更新DOM,暂且略过。现在我们关注genElement函数,代码位置在src/compiler/codegen/index.jsexportfunctiongenElement(el:ASTElement,state:CodegenState):string{if(el.parent){el.pre=el.pre||el.parent.pre}if(el.staticRoot&&!el.staticProcessed){returngenStatic(el,state)//静态节点处理生成函数}elseif(el.once&&!el.onceProcessed){returngenOnce(el,state)//v-once处理生成函数}elseif(el.for&&!el.forProcessed){returngenFor(el,state)//v-for处理生成函数}elseif(el.if&&!el.ifProcessed){returngenIf(el,state)//v-if处理生成器函数}elseif(el.tag==='template'&&!el.slotTarget&&!state.pre){returngenChildren(el,state)||'void0'//子节点处理生成函数}elseif(el.tag==='slot'){returngenSlot(el,state)//槽处理生成函数}else{//组件或元素让代码if(el.component){code=genComponent(el.component,el,state)//组件处理生成函数}else{letdataif(!el.plain||(el.pre&&state.maybeComponent(el))){data=genData(el,state)//attributes节点属性处理生成函数}constchildren=el.inlineTemplate?null:genChildren(el,state,true)code=`_c('${el.tag}'${data?`,${data}`:''//数据}${children?`,${children}`:''//children})`}//模块转换for(leti=0;i
:用`+`v-for渲染的组件列表应该有显式键。`+`参见https://vuejs.org/guide/list.html#key了解更多信息。`,el.rawAttrsMap['v-for'],true/*tip*/)}el.forProcessed=true//避免递归return`${altHelper||'_l'}((${exp}),`+`function(${alias}${iterator1}${iterator2}){`+`return${(allGen||genElement)(el,state)}`+'})'}genFor的逻辑其实就是先从AST元素节点中获取for相关的一些属性,然后返回一个code字符串genDatagenData函数是用于处理节点属性的,源代码位置在src/compiler/codegen/index.jsexportfunctiongenData(el:ASTElement,state:CodegenState):string{letdata='{'//directivesfirst.//指令可能会在生成之前改变el的其他属性。constdirs=genDirectives(el,state)if(dirs)data+=dirs+','//keyif(el.key){data+=`key:${el.key},`}//refif(el.ref){data+=`ref:${el.ref},`}if(el.refInFor){data+=`refInFor:true,`}//preif(el.pre){数据+=`pre:true,`}//使用“is”属性记录组件的原始标签名称if(el.component){data+=`tag:"${el.tag}",`}//模块数据生成functionsfor(leti=0;i