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

如图所示
image.png
然后就到了激动人心的时刻啦,我们需要根据这颗描述节点的树提供的内容把他拼接成可以渲染成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

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

7 声望
0 粉丝
0 条评论
推荐阅读
Vue微信公众号开发踩坑记录
JS-SDK需要向服务端获取签名,且获取签名中需要的参数包括所在页面的url,但由于单页应用的路由特殊,其中涉及到iOS和android微信客户端浏览器内核的差异性导致的兼容问题

imwty132阅读 67.6k评论 81

Vue中的diff算法
diff算法是一种通过同层的树节点进行比较的高效算法,避免了对树进行逐层搜索遍历,所以时间复杂度只有 O(n)。diff算法的在很多场景下都有应用,例如在 vue 虚拟 dom 渲染成真实 dom 的新旧 VNode 节点比较更新时...

款冬27阅读 14.1k评论 7

【已结束】SegmentFault 思否写作挑战赛!
SegmentFault 思否写作挑战赛 是思否社区新上线的系列社区活动在 2 月 8 日 正式面向社区所有用户开启;挑战赛中包含多个可供作者选择的热门技术方向,根据挑战难度分为多个等级,快来参与挑战,向更好的自己前进!

SegmentFault思否20阅读 5k评论 10

封面图
vue-property-decorator使用手册
@Component 装饰器可以接收一个对象作为参数,可以在对象中声明 components ,filters,directives等未提供装饰器的选项,也可以声明computed,watch等

似曾相识17阅读 29.6k评论 7

前端开发工具
前端教程阿西河前端教程丨一个助你成为全栈开发的网站前端高手进阶JavaScript利用js实现表单的校验KeyCode 查询表现代 JavaScript 教程时间戳(Unix timestamp)转换工具HTML minifierJavaScript代码压缩-js代码压...

寒青14阅读 2.6k

万字长文~vue+express+mysql带你彻底搞懂项目中的权限控制(附所有源码)
所谓的权限,其实指的就是:用户是否能看到,以及是否允许其对数据进行增删改查的操作,因为现在开发项目的主流方式是前后端分离,所以整个项目的权限是后端权限控制搭配前端权限控制共同实现的

水冗水孚11阅读 1.5k

[译]Vue.js + Astro 比 Vue SPA 更好吗?
在本文中,我将向您展示如何使用 Astro 构建基于 Vue 的应用程序,我们将了解其独特的架构如何带来比单页应用程序 (SPA) 更好的性能。

杭州程序员张张7阅读 4.1k评论 3

7 声望
0 粉丝
宣传栏