vue中的模板编译的步骤:将template模板转化成ast语法树(拼接字符串),而后通过new Function + with语法,将ast语法树包装成Render函数,而后生成虚构节点,而后将虚构节点挂载到dom树上,生成实在DOM.
(1) 将template模板转换成ast语法树 -parserHTML(正则实现)(2) 对动态语法做动态标记 -markUp(3) 从新生成代码 生成render函数返回的是虚构节点留神:在开发时尽量不要应用template,因为将template转化成render办法,须要在运行时进行编译操作,会有性能损耗,同时援用电邮compiler包的vue体积也会变大。默认.vue文件中的template解决是通过vue-loader(依赖的是vue-template-compiler)来进行解决的而不是通过运行时的编译
应用到的正则// 匹配属性const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/const dynamicArgAttribute = /^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`const qnameCapture = `((?:${ncname}\\:)?${ncname})`// 匹配开始标签开始const startTagOpen = new RegExp(`^<${qnameCapture}`)// 匹配开始标签闭合const startTagClose = /^\s*(\/?)>/// 匹配标签完结const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)const doctype = /^<!DOCTYPE [^>]+>/i
解析开始节点,先匹配到tagName,而后再去循环匹配取的属性的值,而后组装成一个节点
function parseStartTag(){ let start = html.match(startTagOpen) if(start && start.length){ const match = { tagName:start[1], attrs: [] } advance(start[0].length) let end,attr while (!(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) } return match } }
(1代表dom节点,3代表文本节点)
匹配完开始节点后,把曾经匹配过的字符串截取掉,残余字符串接着匹配,上面可能会呈现以下几种状况
(1)接下来匹配就是完结标签
那就祝贺你啦,这个节点曾经齐全匹配进去了,只须要把这个节点从咱们记录的栈里弹出,造成一个上面的构造就OK啦,而后截取点曾经匹配过的字符串,拿剩下的字符串持续进行匹配,直到传入的字符串全副被匹配一遍
{ tag: tagName, type: 1, children: [], attrs:attrs, parent: null }
(2)接下来匹配的还是开始标签
此时咱们匹配到的这个开始标签和咱们上一个标签匹配到的必定是父子关系,从栈里取出最下面的一个节点,如果有值的话,就记录为以后节点的父节点,拿进去的节点的子节点为以后匹配的节点,而后截取点曾经匹配过的字符串,拿剩下的字符串持续进行匹配,直到传入的字符串全副被匹配一遍
(3)接下来匹配的是一段文本,文字就很简略了,用正则匹配到第一个‘<’也就是下一个dom节点的地位),把匹配后获得的字符串去空格解决后,生成一个上面的文本节点就OK啦,而后截取点曾经匹配过的字符串,拿剩下的字符串持续进行匹配,直到传入的字符串全副被匹配一遍
{ text: text, type:3}
等到传入的字符串全副匹配完结当前,会造成一个js形容的节点之间互相关系的dom树
{ tag: 'div', parent: null, attrs:[ { name: 'id', value: 'app' } ], children:[ tag: 'span', parent:{ tag: 'div', parent: null, attrs:[ { name: 'id', value: 'app' } ], children:[...] } ]}
如图所示
而后就到了激动人心的时刻啦,咱们须要依据这颗形容节点的树提供的内容把他拼接成能够渲染成render函数的虚构dom,依照tagName,attrs,children的程序进行解决,最初造成相似上面这样的字符串
_c("div",{id:"app",style:{"width":"20px"}},_c("p",undefined,_v("hello"+_s(name))),_v("hello"))
特地留神的是对于行内款式的解决,要把行内款式的值拼成key-value的模式
if(attr.name === 'style'){ let obj = {} attr.value.split(';').forEach(attrItem => { let [key, value] = attrItem.split(':') obj[key] = value }) attr.value = obj }
而后借助new Function + with就能够把拼接好的字符串转换成render函数了
graph TD A[传入el属性] -->B{判断是否有render办法} B -->|是| C[渲染render办法] B -->|否| D{判断是否有template} D -->|是| E[渲染template] --> F D -->|否| F[渲染el外面的内容] F -->|否|G{判断字符串里是否以箭头结尾} G -->|是| H[应用正则进行匹配] H --> I{用正则匹配是否是开始标签} I --> |是| J[用正则别离取出来标签名和属性]-->T[将开始标签压入栈内]-->M I --> |否| K{用正则匹配是否是完结标签}--> |否| L K --> |是|R[从栈里取出来最下面的标签名,造成闭合的节点] -->U[增加一个dom节点] --> M G --> |否| L[阐明是以文本结尾而后] L--> S[增加一个文字节点] --> M[把曾经匹配过的字段截掉从新进行匹配]--> F F -->|是|N[生成用js形容dom节点的ast语法树] N --> O[拼接包装成字符串] O --> P[通过new Function + with语法生成render函数]--> C
(以上是本人学习过程中整顿,有谬误或者不谨严的中央欢送斧正)