引言
Vue 的编译过程总共分为三个步骤:
- 根据 template 生成 AST(抽象语法树)
- 优化 AST
- 根据 AST 可执行的函数(render 函数之类的)
这三个步骤其实在源码中,也写的清清楚楚。
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
// 生成 AST
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
// 优化 AST
optimize(ast, options)
}
// 生成可执行的函数
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
parse
在 Vue 编译过程中,第一件做的事就是将 template 解析成 AST(抽象语法树,Abstract Syntax tree)。
Vue 中定义的 AST:
ASTElement: {
type: Number, // 1普通元素 2表达式 3纯文本
tag: String, // 例如 ‘p’
arrtsList: Array, //
rawAttrsMap: {}, // 会将 attrsList 抽成对象
parent: ASTElement, // 它的父元素
children: [] // 每个元素都是 ASTElement
start: Number, // 它在整个模板的起始位置
end: Number // 它在整个模板的结束位置
}
PS:其实在 optimize 之后 AST 上还会多出两个属性 static、staticRoot,这个在之后说到 optimize 的时候会提及这两个属性的由来。
而在 Vue 中将 template 生成 AST,则是通过 parse 这个方法。
export function parse (
template: string,
options: CompilerOptions
): ASTElement | void {
....
parseHTML(template, options, {
...
// 遍历 tempalte 根据各种情况生成对应的 AST 树,并形成父子关系
})
return root // 这个就是最终的 AST 树
...
}
在生成 template 对应的 AST 后,会对 AST 进行优化,即遍历 AST 树,对每个 AST 元素进行判断,是否为静态 AST 或 静态 AST Root,为之后的 patch 过程提供依据。
optimize
在 Vue 编译过程给 AST 添加 static 或 staitcRoot,则是通过 optimize 这个方法。
export function optimize (root: ?ASTElement, options: CompilerOptions) {
if (!root) return
...
// 首先对所有的节点进行判断标记,这个添加的是 static 属性
markStatic(root)
// 最后标记父节点,这个添加的是 staticRoot 属性
markStaticRoots(root, false)
}
markStatic
function markStatic (node: ASTNode) {
node.static = isStatic(node)
if (node.type === 1) {
if (
!isPlatformReservedTag(node.tag) &&
node.tag !== 'slot' &&
node.attrsMap['inline-template'] == null
) {
return
}
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i]
markStatic(child)
if (!child.static) {
node.static = false
}
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
const block = node.ifConditions[i].block
markStatic(block)
if (!block.static) {
node.static = false
}
}
}
}
}
其中 isStatic 会对节点进行一些基础的判断,返回 true or false。
如果 AST 的 type 为 2 即表达式,则返回false
如果 AST 的 type 为 3 即纯文本,则返回 true
如果:有 v-pre 指令
没有 v-if 或 v-for 指令
没有 slot 或 component
是平台保留的 HTML 标签
如果不是 tempalte 的直接后代
AST 中没有静态的 key 标识
则返回 true
markStaticRoot
function markStaticRoots (node: ASTNode, isInFor: boolean) {
if (node.type === 1) {
if (node.static || node.once) {
node.staticInFor = isInFor
}
// 这里需要注意 node.static 满足 children.length 存在 长度为1 并且 children 的类别 不能为纯文本类型
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
)) {
node.staticRoot = true
return
} else {
node.staticRoot = false
}
if (node.children) {
for (let i = 0, l = node.children.length; i < l; i++) {
markStaticRoots(node.children[i], isInFor || !!node.for)
}
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
markStaticRoots(node.ifConditions[i].block, isInFor)
}
}
}
}
在 makeStaticRoots 方法中,其实比较特殊的就是判断子节点只有一个的情况,这种情况下,它的子节点不能为纯文本节点(这一点可能是作者做过什么性能测试什么的,所以注释中也写了这样要求获得的会比消耗的性能多)
generate
编译的最后一步,遍历 AST,将优化后的 AST 生成可执行的代码,例如生成 _c、_l、_v 之类的函数。
例如:
<div class="static-class" :class="bindClass">
<span v-for="(item, index) in data" @click="clickMe"></span>
</div>
会 generate 成这样的可执行的函数:
with(this){
return _c('div', {
staticClass: "static-class",
class: bindClass
},
_l((data), function(item, index) {
return _c('span', {
on: {
"click": clickMe
}
}
})
)
}
```