编译器其实是一段程序,用来将源代码翻译成目标代码。译器将源代码翻译为目标代码的过程叫作编译。
编译过程分为编译前端和编译后端。编译前端包含词法分析、语法分析和语义分折,通常和目标平台无关,仅负责分析源代码。编译后端则通常与目标平台有关,编译后端涉及中间代码生成和优化以及目标代码生成。但是,编译后端并不一定会包含中间代码生成和优化这两个环节,这取决于具体的场景和实现。中间代码生成和优化这两个环节有时也叫中端
对于vue.js模板编译器来说,源代码就是组件的模板,而目标代码是能够在浏览器平台上运行的JavaScript代码,或其他拥有JavaScript运行时的平台代码,Vue.js模板编译器的目标代码其实就是渲染函数。
详细来说,Vue.js模板编译器会首先对模板进行词法分析和语法分析,得到模板AST。接着,将模板AST转换为JavaScript AST.最后,根据JavaScript AST生成JavaScript代码,即渲染函数代码。
AST是abstract syntax tree的首字母缩写,即抽象语法树。所谓模板AST,其实就是用描述模板的抽象语法树。例如,有如下模板:
<div>
<h1 v-if="ok">Vue Template</h1>
</div>
这段模板会被编译为如下所示的AST:
const ast = {
// 逻辑根节点
type: 'Root',
children:[
{
// div标签节点
type:'Element',
tag:'div',
children:[
// h1标签节点
{
type:'Element',
tag:'h1',
props:[
// v-if指令节点
{
type: 'Directive', // 类型为Directive 代表指令
name:'if', //指令名称为 if,不带有前缀 v-
exp:{
//表达式节点
type: 'Expression',
content: 'ok'
}
}
]
}
]
}
]
}
可以看到,AST其实就是一个具有层级结构的对象。模板AST具有与模板同构的嵌套结构。每一棵AST都有一个逻辑上的根节点,其类型为Root。模板中真正的根节点则作为Root节点的children存在。
观察上面的AST,我们可以得出如下结论:
- 不同类型的节点是通过节点的type属性进行区分的。例如标签节点的type值为ELement
- 标签节点的子节点存储在其children数组中。
- 标签节点的属性节点和指令节点会存储在props 数组中
- 不同类型的节点会使用不同的对象属性进行描述。例如指令节点拥有 name 属性,用来表达指令的名称,而表达式节点拥有content 属性,用来描述表达式的内容。
可以通过封装parse函数来完成对模板的词法分析和语法分析,得到模板AST,
parse函数接收字符串模板作为参数,并将解析后得到的AST作为返回值返回。
在语义分析的基础上,我们即可得到模板 AST。接着,我们还需要将模板 AST转换为JavaScript AST。因为Vuejs 模板编译器的最终目标是生成渲染函数,而渲染函数本质上是JavaScript代码,所以我们需要将模板AST转换成用于描述渲染函数的JavaScript AST
可以封装transform函数来完成模板AST到JavaScriptAST的转换工作
同样,我们也可以用下面的代码来表达:
const templateAST = parse(template)
const jsAST = transform(templateAST)
有了JavaScrpt AST后,我们就可以根据它生成渲染函数了,这一步可以通过封装generate函数来完成
同样,我们也可以用下面的代码来表达:
const templateAST = parse(template)
const jsAST = transform(templateAST)
const code = generate(jsAST)
generate函数会将渲染函数的代码以字符串的形式返回,并存储在code的常量中