聊聊Vue.js的模板编译的问题
写在前面
因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出。
文章的原地址:https://github。com/answershuto/learn vue。
在学习过程中,为某视频剪辑软件加上了中文的注释https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以对其他想学习某视频剪辑软件源码的小伙伴有所帮助。
可能会有理解存在偏差的地方,欢迎提问题指出,共同学习,共同进步。
$mount
首先看一下增加的代码
/*把原本不带编译的$mount方法保存下来,在最后会调用*/const mount=Vue。原型。$ mount/*挂载组件,带模板编译*/Vue。原型。$ mount=function(El?弦|元素,补水?布尔值):组件{ el=el查询(el) /*伊斯坦布尔忽略if */if(El===文档。正文| | El===文档。文档元素){流程。ENV。node _ ENV!=='production' warn('不要将某视频剪辑软件挂载到超文本标记语言或车身安装到普通元素.` )返回此}常量选项=此.$options //解析模板/el并转换为渲染函数/*处理模板模板,编译成提出函数,渲染不存在的时候才会编译模板,否则优先使用render*/if(!options . render){ 0让模板=options . template/*模板存在的时候取模板,不存在的时候取埃尔的外套HTML*/if(模板){ /*当模板是字符串的时候*/if(模板类型==' string '){ if(模板。charat(0)='=' # '){ template=idToTemplate(template)/*伊斯坦布尔忽略if */if (process.env.NODE_ENV!=='生产!模板){ warn(` 0找不到模板元素或为empty: ${options.template} `,this)} } else if(template。nodetype){/*当模板为数字正射影像图节点的时候*/template=模板。innerhtml } else {/*报错*/if (process.env.NODE_ENV!==“production”){ warn(”无效的模板选项: '模板,这个)}返回this } } else if (el) { /*获取元素的outer HTMl */template=get outer HTMl(El)} if(template){/*伊斯坦布尔忽略if */if (process.env.NODE_ENV!==“生产”配置。性能标记){标记('编译')}/*将模板编译成提出函数,这里会有提出以及staticRenderFns两个返回,这是某视频剪辑软件的编译时优化静态的静态不需要在VNode更新时进行补丁,优化性能*/const { render,static render fns }=CompileToFunctions(模板,{ shouldDecodeNewlines,delimiters:选项。分隔符},这)选项。渲染=渲染选项。静态renderfns=静态renderfns/*伊斯坦布尔忽略if */if (process.env.NODE_ENV!==“生产”配置。性能标记){标记('编译结束')度量(` $ { this ._name}编译`、'编译'、'编译结束')} } }/* github :https://github。com/answershuto *//*调用const mount=Vue。原型。$ mount保存下来的不带编译的mount*/return mount.call(this,el,补水)}通过增加代码我们可以看到,在增加的过程中,如果提出函数不存在(渲染函数存在会优先使用渲染)会将模板进行compileToFunctions得到提出以及静态渲染Fns。譬如说手写组件时加入了模板的情况都会在运行时进行编译。而渲染功能在运行后会返回VNode节点,供页面的渲染以及在更新的时候补丁。接下来我们来看一下模板是如何编译的。
一些基础
首先,模板会被编译成大西洋时间语法树,那么大西洋时间是什么?
在计算机科学中,抽象语法树(抽象语法树或者缩写为AST),或者语法树(语法树),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。
大西洋时间会经过产生得到提出函数,渲染的返回值是虚拟代码是某视频剪辑软件的虚拟数字正射影像图节点,具体定义如下:
导出默认类VNode { tag:字符串| voiddata: VNodeData | void儿童:ArrayVNodetext:字符串| voidelm:节点|无效;ns:字符串| voidcontext:组件|无效;//在此组件的范围内呈现功能上下文:组件| void//仅适用于功能组件根节点key:字符串|数字|无效组件选项3360 VnodeComponentOptions | void;组件实例:组件|无效;//组件实例parent: VNode | void//组件占位符节点raw:布尔值;//包含原始HTML?(仅服务器)isStatic:布尔值;//提升的静态节点isRootInsert:布尔值;//输入转换检查所必需的是:布尔值;//空注释占位符?isCloned:布尔值;//是克隆节点吗?isOnce:布尔值;//是一次节点吗?/* github :3359 github。com/answershuto */constructor(标记?字符串,数据? VNodeData,孩子们?ArrayVNode,文本?弦,榆树?节点,上下文?组件,组件选项? VNodeComponentOptions ) { /*当前节点的标签名*/this.tag=tag /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/this.data=data /*当前节点的子节点,是一个数组*/this.children=children /*当前节点的文本*/this.text=text /*当前虚拟节点对应的真实数字正射影像图节点*/this.elm=elm /*当前节点的名字空间*/this.ns=undefined /*编译作用域*/this.context=context /*函数化组件作用域*/这个。函数上下文=未定义/*节点的键属性,被当作节点的标志,用以优化*/this.key=data data.key /*组件的选择权选项*/这个。组件选项=组件选项/*当前节点对应的组件的实例*/这个。组件实例=未定义/*当前节点的父节点*/this.parent=undefined /*简而言之就是是否为原生超文本标记语言或只是普通文本,innerHTML的时候为真,文本内容的时候为false*/this.raw=false /*静态节点标志*/this.isStatic=false /*是否作为跟节点插入*/this.isRootInsert=true /*是否为注释节点*/this.isComment=false /*是否为克隆节点*/这个。iscold=false/*是否有一次指令*/这个。is once=false }//DEVERATE :向后兼容的组件实例的别名。/*伊斯坦布尔忽略下一个*/get child(): Component | void { return this。组件实例} }关于VNode的一些细节,请参考VNode节点。
创建编译器
创建编译器用以创建编译器,返回值是编制以及compileToFunctions。编制是一个编译器,它会将传入的模板转换成对应的大西洋时间树、渲染函数以及staticRenderFns函数。而compileToFunctions则是带缓存的编译器,同时staticRenderFns以及提出函数会被转换成功能对象。
因为不同平台有一些不同的选项,所以创建编译器会根据平台区分传入一个基本选项,会与编制本身传入的选择合并得到最终的最终选项。
compileToFunctions
首先还是贴一下compileToFunctions的代码。
/*带缓存的编译器,同时staticRenderFns以及提出函数会被转换成功能对象*/function CompileToFunctions(模板:字符串,选项?编译器选项vm?组件):编译函数结果{ options=options | | { }/*伊斯坦布尔忽略if */if (process.env.NODE_ENV!=='production') { //检测可能的芯片尺寸封装限制尝试{新函数(' return 1 ')} catch(e){ if(e . tostring().匹配(/unsafe-eval | CSP/){ warn('您似乎在' '具有禁止不安全-评估的内容安全策略的环境中使用Vue.js的独立版本。'模板编译器无法在此环境中工作。请考虑"放宽策略,允许不安全的评估或将您的"模板预编译到呈现函数中)} } }/* github :https://github。com/answershuto *///检查缓存/*有缓存的时候直接取出缓存中的结果即可*/const键=选项。分隔符?字符串(选项。分隔符)模板:模板if(functionCompileCache[key]){ return functionCompileCache[key]}//compile/*编译*/const编译=编译(模板,选项)//检查编译错误/提示if (process.env.NODE_ENV!==' production '){ if(已编译。编译的错误。错误。length){ warn(`错误编译模板: \ n \ n $ { template } \ n \ n `已编译。错误。map(e=`-$ { e } ` ).join('\n') '\n ',VM)} if(已编译。编译的提示。小贴士。长度){已编译。小贴士。foreach(msg=tip(msg,VM))}//将代码转换为函数const RES={ } const fngeners=[]/*将提出转换成功能对象*/RES . render=MakeFuncTion(已编译。render,fngenerors将staticRenderFns全部转化成功能对象*/const l=已编译。stationrenderfns。长度RES . stationrenderfns=的新数组(l)让I=0;我。I){ RES . static renderfns[I]=make函数(已编译。静态renderfns[I],fngenerators)}//检查函数生成错误。//只有当编译器本身存在病菌时,才会出现这种情况。//主要用于codegen开发使用/*伊斯坦布尔忽略if */if (process.env.NODE_ENV!=='production') { if((!compiled.errors ||!已编译。错误。长度)fngenerors。长度){ warn(` 0未能生成呈现函数: \ n \ n ` fngenerors。map({ err,code })=` $ { err。tostring()} in \ n \ n $[code]\ n `).join('\n '),vm ) } } /*存放在缓存中,以免每次都重新编译*/return(functionCompileCache[key]=RES)}我们可以发现,在闭包中,会有一个functionCompileCache对象作为缓存器。
/*作为缓存,防止每次都重新编译*/const functioncompilecache : {[key : string]:编译后的functionresult}=对象。创建(空)在进入compileToFunctions以后,会先检查缓存中是否有已经编译好的结果,如果有结果则直接从缓存中读取。这样做防止每次同样的模板都要进行重复的编译工作。
//检查缓存/*有缓存的时候直接取出缓存中的结果即可*/const键=选项。分隔符?字符串(选项。分隔符)模板:模板if(functionCompileCache[key]){ 0返回functionCompileCache[key] }在compileToFunctions的末尾会将编译结果进行缓存
/*存放在缓存中,以免每次都重新编译*/return(functionCompileCache[key]=RES)编译
/*编译,将模板模板编译成大西洋时间树、渲染函数以及staticRenderFns函数*/函数编译(模板:字符串,选项?编译选项):编译结果{ const final options=object。创建(基本选项)常量错误=[]常量提示=[]最终选项。warn=(msg,tip)={ (tip?提示:错误)。推送(消息)} /*做下面这些合并的目的因为不同平台可以提供自己本身平台的一个基本选项,内部封装了平台自己的实现,然后把共同的部分抽离开来放在这层编译程序中,所以在这里需要合并一下*/if(选项){ //合并自定义模块/*合并模块*/if(选项。模块){最终选项。模块=(基本选项。模块| |[]).concat(options.modules) } //合并自定义指令if(选项。指令){ /*合并指令*/finalOptions。指令=extend(对象。创建(基本选项。指令),选项。指令)} //复制其他选项为(选项中的常量键){ /*合并其余的选项、模块与指令已经在上面做了特殊处理了*/if(键!==“模块”键!=='指令){最终选项[键]=选项[键]} } }/*基础模板编译,得到编译结果*/const compiled=baseCompile(模板,最终选项)if(过程。ENV。NODE _ ENV!==' production '){错误。用力。应用(错误,检测错误(已编译。ast))}编译。错误=编译错误。tips=tips return compiled }编译主要做了两件事,一件是合并选项(前面说的将平台自有的选择权与传入的选择权进行合并),另一件是baseCompile,进行模板模板的编译。
来看一下基础编译
基础编译
函数baseCompile (template:字符串,选项:编译选项):编译结果{/*解析解析得到大西洋标准时间树*/const ast=parse(template.trim(),options) /*将大西洋时间树进行优化优化的目标:生成模板大西洋时间树,检测不需要进行数字正射影像图改变的静态子树。一旦检测到这些静态树,我们就能做以下这些事情: 1.把它们变成常数,这样我们就再也不需要每次重新渲染时创建新的节点了。 2.在修补的过程中直接跳过*/优化(ast,选项)/*根据大西洋标准时间树生成所需的代码(内部包含提出与静态render fns)*/const code=generate(ast,options)返回{ ast,render: code.render,static renderfns 3360 code。静态renderfns } }基础编译首先会将模板模板进行从语法上分析得到一个大西洋时间语法树,再通过使最优化做一些优化,最后通过产生得到提出以及静态渲染Fns。
从语法上分析
从语法上分析的源码可以参见https://github。com/answershuto/learn vue/blob/master/vue-src/编译器/解析器/索引。js # L53。
从语法上分析会用正则等方式解析模板模板中的指令、阶级、风格等数据,形成大西洋时间语法树。
使最优化
使最优化的主要作用是标记静电静态节点,这是某视频剪辑软件在编译过程中的一处优化,后面当更新更新界面时,会有一个修补的过程,差异算法会直接跳过静态节点,从而减少了比较的过程,优化了修补的性能。
产生
产生是将大西洋时间语法树转化成渲染功能字符串的过程,得到结果是提出的字符串以及staticRenderFns字符串。
至此,我们的模板模板已经被转化成了我们所需的大西洋时间语法树、渲染功能字符串以及staticRenderFns字符串。
举个例子
来看一下这段代码的编译结果
div class=' main ' : class=' BindClass ' div { { text } }/div div hello world/div v v-for='(item,index)在arr ' p { { item }。name } }/p p { { item }。value } }/p { { index } }/p p-/p/div v-if=' text ' { text } }/div v-else/div/div转化后得到大西洋时间树,如下图:
我们可以看到最外层的div是AST树的根节点,节点上有很多数据表示这个节点的形式,比如static表示它是否是一个静态节点,staticClass表示一个静态类属性(不是bind:class)。Children表示这个节点的子节点,可以看到children是一个长度为4的数组,在这个节点下包含4个div子节点。子节点的结构与父节点相似,形成一层一层的AST语法树。
让我们来看看AST获得的渲染函数
with(this){ return _c('div ',{/* static class */static class : ' main ',/* bind class */class : bind class },[ _c('div ',[_v(_s(text))]),_c('div ',[_v('hello world ')))),/*这是一个v-for循环*/_ l ((arr),函数(item,index) {return _ c ('div ',[_ c ('p ',)name))) _ c ('p ',[_ v (_ s (index))]),_ c ('p ',[_ v('-')])}),/*这是v-if*/(text)?_c('div ',[_v(_s(text))]):_c('div ',[_v('无文本')])],2 )}_c,_v,_s,_q
看渲染函数字符串,我们发现了大量的_c,_v,_s,_ q,这些是什么函数?
带着问题,让我们看看核心/实例/渲染。
/*处理v-once的渲染函数*/Vue.prototype._o=markOnce /*将字符串转换为数字。如果转换失败,它将返回原始字符串*/Vue.prototype._n=toNumber /*将val转换为字符串*/Vue . prototype . _ s=ToString/* handle v-for list rendering */Vue。原型。_ l=renderlist/*处理槽渲染*/vue . prototype . _ t=render slot/*检测两个变量是否相等*/Vue。原型。_ Q=松散相等/*检测arr数组是否包含与值变量相等的项*/VUE。原型。_ I=处理静态树的呈现*/vue . prototype . _ m=render static/*处理过滤器*/vue . prototype . _ f=resolvefilter/*从配置中检查eventKeyCode是否存在*/vue . prototype . _ k=check key codes/*将v-bind指令合并到VNode */vue . prototype . _ b=bind objectprops/*创建文本节点*/vue . prototype . _ v=createtextVNode/*创建空的VNode节点*/vue . prototype . _ I_ c=(a,B,c,d)=createelement (VM,a,B,c,d,false)通过这些函数,渲染函数最终会返回一个VNode节点,并在_update的时候,通过patch将其与之前的VNode节点进行比较,得到差异,然后将这些差异渲染到真实的DOM中。
以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。
版权声明:聊聊Vue.js的模板编译的问题是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。