vue模版编译流程

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:[...]
		}
	]
}

如图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ys0T1mrl-1638887538147)(/img/bVcWvuN)]
然后就到了激动人心的时刻啦,我们需要根据这颗描述节点的树提供的内容把他拼接成可以渲染成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函数了

传入el属性
判断是否有render方法
渲染render方法
判断是否有template
渲染template
渲染el里面的内容
判断字符串里是否以箭头开头
使用正则进行匹配
用正则匹配是否是开始标签
用正则分别取出来标签名和属性
将开始标签压入栈内
把已经匹配过的字段截掉重新进行匹配
用正则匹配是否是结束标签
说明是以文本开头然后
从栈里取出来最上面的标签名,形成闭合的节点
添加一个dom节点
添加一个文字节点
生成用js描述dom节点的ast语法树
拼接包装成字符串
通过new Function + with语法生成render函数

(以上是自己学习过程中整理,有错误或者不严谨的地方欢迎指正)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值