当前位置: 首页 > Web前端 > vue.js

vue模板编译过程

时间:2023-03-31 15:19:36 vue.js

vue中模板编译的步骤:将模板模板转换成ast语法树(拼接字符串),然后通过newFunction+with语法将ast语法树包裹成Render函数,然后生成虚拟节点,然后挂载virtualnode去dom树生成一个真实的DOM。(1)将template模板转换为ast语法树——parserHTML(正则实现)(2)为静态语法做静态标记——markUp(3)重新生成代码生成render函数返回一个虚拟Node注:尝试开发时不要使用模板,因为将模板转换为render方法需要在运行时进行编译操作,会造成性能损失,同时vue引用email编译包的体积也会增加。默认.vue文件中的模板处理是通过vue-loader(依赖vue-template-compiler)处理的,而不是运行时编译使用的正则//匹配属性constattribute=/^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/constdynamicArgAttribute=/^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/constncname=`[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`constqnameCapture=`((?:${ncname}\\:)?${ncname})`//匹配开始标签startconststartTagOpen=newRegExp(`^<${qnameCapture}`)//匹配开始标签closureconststartTagClose=/^\s*(\/?)>///匹配标签结束constendTag=newRegExp(`^<\\/${qnameCapture}[^>]*>`)constdoctype=/^/i解析起始节点,先匹配tagName,再去循环匹配得到的属性值,然后组装成节点函数parseStartTag(){letstart=html.match(startTagOpen)if(start&&start.length){constmatch={tagName:start[1],attrs:[]}advance(start[0].length)letend,attrwhile(!(end=html.match(startTagClose))&&(attr=html.match(attribute))){advance(attr[0].length)match.attrs.push({name:attr[1],value:attr[3]||attr[4]||attr[5]})}if(end){advance(end[0].length)}returnmatch}}(1代表dom节点,3代表text节点)匹配起始节点后,截取匹配的字符串,然后再匹配剩下的字符串,可能会出现以下几种情况(1)下一个匹配的是结束标签那么恭喜,这个节点已经匹配完全了,直接把这个节点从我们的记录栈中pop出来,形成就OK了如下结构,然后截取匹配到的字符串,继续匹配剩余的宁字符串,直到所有传入的字符串都匹配{tag:tagName,type:1,children:[],attrs:attrs,parent:null}(2)下一个匹配是起始标签。这时候我们匹配的起始标签和我们匹配的上一个标签肯定是父子关系。从栈中取出栈顶节点,如果有值,则记录为当前节点的父节点,取出的节点的子节点为当前匹配的节点,则匹配到的字符串为截取,用剩下的字符串继续匹配,直到传入的字符串全部匹配一次(3)接下来匹配的是一段文本,文本很简单,用正则模式匹配第一个'<',也就是下一个dom节点的位置),并且将匹配后得到的字符串去掉空格最后在下面生成一个文本节点就OK了,然后截取匹配到的字符串,继续匹配剩下的字符串,直到全部匹配传入的字符串{text:text,type:3}匹配完所有传入的字符串后,会形成一个描述节点间关系的dom树{tag:'div',parent:null,attrs:[{name:'id',值:'app'}],children:[tag:'span',parent:{tag:'div',parent:null,attrs:[{name:'id',value:'app'}],children:[...]}]}如图,激动人心的时刻来了。我们需要把它拼接成一个虚拟的dom,这个dom可以根据描述节点的树提供的内容渲染成render函数。按照tagName、attrs、childrenProcess的顺序,最后形成如下字符串_c("div",{id:"app",style:{"width":"20px"}},_c("p",undefined,_v("hello"+_s(name))),_v("hello"))特别注意行内样式原因是将内联样式的值拼成key-value的形式if(attr.name==='style'){letobj={}attr.value.split(';').forEach(attrItem=>{let[key,value]=attrItem.split(':')obj[key]=value})attr.value=obj}然后使用newFunction+with将拼接后的字符串转为render函数图TDA[输入el属性]-->B{判断是否有render方法}B-->|有|C[渲染方法]B-->|否|D{判断是否有模板}D-->|是|E[渲染模板]-->FD-->|否|F[渲染el中的内容]F-->|No|G{判断字符串是否以箭头开头}G-->|Yes|H[使用正则匹配]H-->I{使用正则匹配是否为开始标签}I-->|yes|内]-->MI-->|No|K{使用正则匹配是否为结束标签}-->|否|LK-->|Yes|R[从栈中取出最上面的标签name,组成闭包节点]-->U[添加一个dom节点]-->MG-->|NO|匹配的字段被截断重新匹配]-->FF-->|是|N[用js生成描述dom节点的ast语法树]N-->O[拼接并打包成字符串]O-->P[通过newFunction+withsyntax生成render函数]-->C(以上是我自己学习过程中整理的,如有错误或不严谨请指正)