Vue.js模板编译全流程详解
本文目标
学完本文,你将能够:
- 理解Vue模板如何转换为可执行的JavaScript代码
- 掌握模板解析和AST生成的完整过程
- 了解Vue的编译优化策略和性能提升原理
- 能够实现一个简化版的模板编译器
- 明确运行时编译和预编译的应用场景
系列导航
上一篇: Virtual DOM实现详解 | 下一篇: Diff算法深度剖析
引言:从模板字符串到可执行代码
在Vue.js开发中,我们经常编写这样的模板:
<div id="app">
<h1>{{ title }}</h1>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
<button @click="handleClick">点击我</button>
</div>
但浏览器并不认识这些Vue特有的语法。那么,Vue是如何将这些模板字符串转换成可以在浏览器中执行的JavaScript代码的呢?这就是模板编译的神奇之处。
模板编译的整体架构
Vue的模板编译过程可以分为三个主要阶段:
让我们逐步深入每个阶段的实现细节。
第一阶段:模板解析(Parse)
1. 词法分析基础
模板解析的第一步是将模板字符串分解成一个个的token(标记):
// 简化版的HTML解析器
class HTMLParser {
constructor(template) {
this.template = template;
this.currentIndex = 0;
this.stack = []; // 标签栈,用于处理嵌套
}
// 解析模板的主函数
parse() {
const ast = {
type: 'Root',
children: []
};
this.stack.push(ast);
while (this.currentIndex < this.template.length) {
const char = this.template[this.currentIndex];
if (char === '<') {
this.parseTag();
} else {
this.parseText();
}
}
return ast;
}
// 解析标签
parseTag() {
const tagMatch = this.template.slice(this.currentIndex).match(/^<\/?([a-zA-Z][a-zA-Z0-9-]*)/);
if (!tagMatch) {
this.currentIndex++;
return;
}
const isClosingTag = tagMatch[0][1] === '/';
const tagName = tagMatch[1];
if (isClosingTag) {
// 处理闭合标签
this.handleClosingTag(tagName);
} else {
// 处理开始标签
this.handleOpeningTag(tagName);
}
}
// 处理开始标签
handleOpeningTag(tagName) {
const element = {
type: 'Element',
tag: tagName,
attributes: [],
directives: [],
children: []
};
// 解析属性
this.parseAttributes(element);
// 添加到父元素
const parent = this.stack[this.stack.length - 1];
parent.children.push(element);
// 如果不是自闭合标签,入栈
if (!this.isSelfClosing(tagName)) {
this.stack.push(element);
}
}
// 解析属性和指令
parseAttributes(element) {
let match;
const attrRegex = /\s*([^\s"'<>\/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
while (match = attrRegex.exec(this.template.slice(this.currentIndex))) {
const name = match[1];
const value = match[2] || match[3] || match[4] || true;
if (name.startsWith('v-')) {
// Vue指令
element.directives.push({
name: name.slice(2),
value: value,
arg: null,
modifiers: {}
});
} else if (name.startsWith(':')) {
// 绑定属性
element.attributes.push({
name: name.slice(1),
value: value,
dynamic: true
});
} else if (name.startsWith('@')) {
// 事件监听
element.directives.push({
name: 'on',
value: value,
arg: name.slice(1),
modifiers: {}
});
} else {
// 普通属性
element.attributes.push({
name: name,
value: value,
dynamic: false
});
}
}
}
// 解析文本内容
parseText() {
const endIndex = this.template.indexOf('<', this.currentIndex);
const text = this.template.slice(
this.currentIndex,
endIndex === -1 ? undefined : endIndex
);
if (text.trim()) {
// 解析插值表达式
const tokens = this.parseInterpolation(text);
const parent = this.stack[this.stack.length - 1];
tokens.forEach(token => {
parent.children.push(token);
});
}
this.currentIndex = endIndex === -1 ? this.template.length : endIndex;
}
// 解析插值表达式
parseInterpolation(text) {
const tokens = [];
const regex = /\{\{(.*?)\}\}/g;
let lastIndex = 0;
let match;
while (match = regex.exec(text)) {
// 添加插值前的纯文本
if (match.index > lastIndex) {
tokens.push({
type: 'Text',
content: text.slice(lastIndex, match.index)
});
}
// 添加插值表达式
tokens.push({
type: 'Interpolation',
expression: match[1].trim()
});
lastIndex = match.index + match[0].length;
}
// 添加剩余的纯文本
if (lastIndex < text.length) {
tokens.push({
type: 'Text',
content: text.slice(lastIndex)
});
}
return tokens;
}
}
2. AST节点设计
Vue的AST节点包含了丰富的信息,用于后续的优化和代码生成:
// AST节点类型定义
const ASTNodeTypes = {
ROOT: 'Root',
ELEMENT: 'Element',
TEXT: 'Text',
INTERPOLATION: 'Interpolation',
ATTRIBUTE: 'Attribute',
DIRECTIVE: 'Directive'
};
// 创建AST节点的工厂函数
function createASTElement(tag, attrs, parent) {
return {
type: ASTNodeTypes.ELEMENT,
tag,
attrsList: attrs,
attrsMap: makeAttrsMap(attrs),
parent,
children: [],
// 编译优化相关
static: false, // 是否是静态节点
staticRoot: false, // 是否是静态根节点
staticInFor: false, // 是否是v-for内的静态节点
// 指令相关
hasBindings: false, // 是否有动态绑定
if: undefined, // v-if表达式
for: undefined, // v-for表达式
key: undefined, // :key值
ref: undefined, // ref引用
// 事件相关
events: undefined, // 事件处理器
nativeEvents: undefined, // 原生事件处理器
// 其他
slotName: undefined, // 插槽名称
component: undefined // 动态组件
};
}
// 属性数组转Map
function makeAttrsMap(attrs) {
const map = {};
for (let i = 0; i < attrs.length; i++) {
map[attrs[i].name] = attrs[i].value;
}
return map;
}
第二阶段:优化(Optimize)
优化阶段的主要目的是标记静态节点,这样在后续的更新过程中可以跳过这些节点的比对:
// 优化器实现
class Optimizer {
constructor(ast) {
this.ast = ast;
}
// 优化入口
optimize() {
if (!this.ast) return;
// 第一步:标记静态节点
this.markStatic(this.ast);
// 第二步:标记静态根节点
this.markStaticRoots(this.ast);
return this.ast;
}
// 标记静态节点
markStatic(node) {
node.static = this.isStatic(node);
if (node.type === ASTNodeTypes.ELEMENT) {
// 如果节点是静态的,它的子节点也必须都是静态的
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
this.markStatic(child);
if (!child.static) {
node.static = false;
}
}
// 处理条件渲染
if (node.ifConditions) {
for (let i = 1; i < node.ifConditions.length; i++) {
const block = node.ifConditions[i].block;
this.markStatic(block);
if (!block.static) {
node.static = false;
}
}
}
}
}
// 判断节点是否是静态的
isStatic(node) {
if (node.type === ASTNodeTypes.INTERPOLATION) {
return false; // 插值表达式一定是动态的
}
if (node.type === ASTNodeTypes.TEXT) {
return true; // 纯文本一定是静态的
}
// 元素节点的静态判断
return !!(
!node.hasBindings && // 没有动态绑定
!node.if && !node.for && // 没有v-if或v-for
!this.isBuiltInTag(node.tag) && // 不是内置标签
this.isPlatformReservedTag(node.tag) && // 是平台保留标签
!this.isDirectChildOfTemplateFor(node) && // 不是template标签的直接子节点
Object.keys(node).every(key => this.isStaticKey(key)) // 所有属性都是静态的
);
}
// 标记静态根节点
markStaticRoots(node) {
if (node.type === ASTNodeTypes.ELEMENT) {
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === ASTNodeTypes.TEXT
)) {
// 如果一个静态节点只包含一个文本子节点,
// 那么它不会被标记为静态根节点,因为优化成本会超过收益
node.staticRoot = true;
return;
} else {
node.staticRoot = false;
}
// 递归标记子节点
if (node.children) {
for (let i = 0; i < node.children.length; i++) {
this.markStaticRoots(node.children[i]);
}
}
// 处理条件渲染的其他分支
if (node.ifConditions) {
for (let i = 1; i < node.ifConditions.length; i++) {
this.markStaticRoots(node.ifConditions[i].block);
}
}
}
}
// 辅助方法
isBuiltInTag(tag) {
return tag === 'slot' || tag === 'component';
}
isPlatformReservedTag(tag) {
// 简化实现,实际中会根据平台不同有所区别
const reservedTags = ['div', 'span', 'p', 'a', 'img', 'ul', 'li', 'h1', 'h2', 'h3', 'button'];
return reservedTags.includes(tag);
}
isStaticKey(key) {
const staticKeys = ['type', 'tag', 'attrsList', 'attrsMap', 'plain', 'parent', 'children', 'attrs', 'static', 'staticRoot'];
return staticKeys.includes(key);
}
}
静态提升优化示例
让我们通过一个具体的例子来理解静态优化的效果:
// 优化前的模板
const template = `
<div>
<h1>静态标题</h1>
<p>{{ dynamicText }}</p>
<ul>
<li>静态列表项1</li>
<li>静态列表项2</li>
<li>{{ dynamicItem }}</li>
</ul>
</div>
`;
// 经过优化后的AST标记
const optimizedAST = {
type: 'Element',
tag: 'div',
static: false, // 因为包含动态子节点
children: [
{
type: 'Element',
tag: 'h1',
static: true,
staticRoot: true, // 静态根节点,可以被提升
children: [{ type: 'Text', content: '静态标题', static: true }]
},
{
type: 'Element',
tag: 'p',
static: false, // 包含插值表达式
children: [{ type: 'Interpolation', expression: 'dynamicText', static: false }]
},
{
type: 'Element',
tag: 'ul',
static: false, // 包含动态子节点
children: [
{
type: 'Element',
tag: 'li',
static: true,
children: [{ type: 'Text', content: '静态列表项1', static: true }]
},
{
type: 'Element',
tag: 'li',
static: true,
children: [{ type: 'Text', content: '静态列表项2', static: true }]
},
{
type: 'Element',
tag: 'li',
static: false,
children: [{ type: 'Interpolation', expression: 'dynamicItem', static: false }]
}
]
}
]
};
第三阶段:代码生成(Generate)
代码生成阶段将优化后的AST转换为可执行的渲染函数:
// 代码生成器
class CodeGenerator {
constructor(ast, options = {}) {
this.ast = ast;
this.options = options;
this.staticRenderFns = [];
this.staticRenderFnIndex = 0;
}
// 生成代码入口
generate() {
const code = this.ast ? this.genElement(this.ast) : '_c("div")';
return {
render: `with(this){return ${code}}`,
staticRenderFns: this.staticRenderFns
};
}
// 生成元素代码
genElement(el) {
if (el.static && !el.staticProcessed) {
return this.genStatic(el);
} else if (el.for && !el.forProcessed) {
return this.genFor(el);
} else if (el.if && !el.ifProcessed) {
return this.genIf(el);
} else {
// 普通元素
let code;
if (el.component) {
code = this.genComponent(el);
} else {
const data = this.genData(el);
const children = this.genChildren(el);
code = `_c('${el.tag}'${
data ? `,${data}` : ''
}${
children ? `,${children}` : ''
})`;
}
return code;
}
}
// 生成静态节点代码
genStatic(el) {
el.staticProcessed = true;
// 将静态节点的渲染函数单独保存
this.staticRenderFns.push(`with(this){return ${this.genElement(el)}}`);
// 返回对静态渲染函数的调用
return `_m(${this.staticRenderFnIndex++})`;
}
// 生成v-for代码
genFor(el) {
el.forProcessed = true;
const exp = el.for;
const alias = el.alias;
const iterator1 = el.iterator1 ? `,${el.iterator1}` : '';
const iterator2 = el.iterator2 ? `,${el.iterator2}` : '';
return `_l((${exp}),` +
`function(${alias}${iterator1}${iterator2}){` +
`return ${this.genElement(el)}` +
'})';
}
// 生成v-if代码
genIf(el) {
el.ifProcessed = true;
return this.genIfConditions(el.ifConditions.slice());
}
genIfConditions(conditions) {
if (!conditions.length) {
return '_e()'; // 空节点
}
const condition = conditions.shift();
if (condition.exp) {
return `(${condition.exp})?${
this.genElement(condition.block)
}:${
this.genIfConditions(conditions)
}`;
} else {
return `${this.genElement(condition.block)}`;
}
}
// 生成数据对象
genData(el) {
let data = '{';
// 处理指令
if (el.directives) {
const dirs = this.genDirectives(el.directives);
if (dirs) data += dirs + ',';
}
// 处理key
if (el.key) {
data += `key:${el.key},`;
}
// 处理ref
if (el.ref) {
data += `ref:${el.ref},`;
}
// 处理属性
if (el.attrs) {
data += `attrs:{${this.genProps(el.attrs)}},`;
}
// 处理DOM属性
if (el.props) {
data += `domProps:{${this.genProps(el.props)}},`;
}
// 处理事件
if (el.events) {
data += `${this.genHandlers(el.events)},`;
}
// 处理原生事件
if (el.nativeEvents) {
data += `${this.genHandlers(el.nativeEvents, true)},`;
}
// 处理插槽
if (el.slot) {
data += `slot:${el.slot},`;
}
data = data.replace(/,$/, '') + '}';
return data;
}
// 生成子节点代码
genChildren(el) {
const children = el.children;
if (children.length) {
return `[${children.map(c => this.genNode(c)).join(',')}]`;
}
}
// 生成节点代码
genNode(node) {
if (node.type === ASTNodeTypes.ELEMENT) {
return this.genElement(node);
} else if (node.type === ASTNodeTypes.TEXT) {
return this.genText(node);
} else if (node.type === ASTNodeTypes.INTERPOLATION) {
return this.genInterpolation(node);
}
}
// 生成文本代码
genText(text) {
return `_v(${JSON.stringify(text.content)})`;
}
// 生成插值表达式代码
genInterpolation(node) {
return `_s(${node.expression})`;
}
// 生成属性代码
genProps(props) {
let res = '';
for (let i = 0; i < props.length; i++) {
const prop = props[i];
res += `"${prop.name}":${prop.dynamic ? prop.value : JSON.stringify(prop.value)},`;
}
return res.slice(0, -1);
}
// 生成事件处理代码
genHandlers(events, native) {
const prefix = native ? 'nativeOn:' : 'on:';
let res = prefix + '{';
for (const name in events) {
res += `"${name}":${this.genHandler(events[name])},`;
}
return res.slice(0, -1) + '}';
}
genHandler(handler) {
if (!handler) {
return 'function(){}';
}
if (Array.isArray(handler)) {
return `[${handler.map(h => this.genHandler(h)).join(',')}]`;
}
const isMethodPath = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(handler.value);
const isFunctionExpression = /^function\s*\(/.test(handler.value);
if (isMethodPath) {
return handler.value;
} else if (isFunctionExpression) {
return handler.value;
} else {
return `function($event){${handler.value}}`;
}
}
}
完整的模板编译器实现
现在让我们将所有部分组合起来,实现一个完整的简化版模板编译器:
// Vue模板编译器
class VueTemplateCompiler {
constructor() {
this.cache = new Map(); // 编译缓存
}
// 编译模板
compile(template, options = {}) {
// 检查缓存
const cached = this.cache.get(template);
if (cached) {
return cached;
}
// 1. 解析:模板 -> AST
const parser = new HTMLParser(template);
const ast = parser.parse();
// 2. 优化:标记静态节点
const optimizer = new Optimizer(ast);
optimizer.optimize();
// 3. 代码生成:AST -> 渲染函数
const generator = new CodeGenerator(ast, options);
const code = generator.generate();
// 创建渲染函数
const render = this.createFunction(code.render);
const staticRenderFns = code.staticRenderFns.map(code =>
this.createFunction(code)
);
const result = {
ast,
render,
staticRenderFns
};
// 缓存结果
this.cache.set(template, result);
return result;
}
// 创建函数
createFunction(code) {
try {
return new Function(code);
} catch (e) {
console.error('编译错误:', e);
return () => {};
}
}
}
// 运行时辅助函数
const helpers = {
// 创建元素VNode
_c(tag, data, children) {
return {
tag,
data,
children,
text: undefined,
elm: undefined,
key: data && data.key
};
},
// 创建文本VNode
_v(text) {
return {
text,
tag: undefined,
data: undefined,
children: undefined
};
},
// toString
_s(val) {
return val == null
? ''
: typeof val === 'object'
? JSON.stringify(val, null, 2)
: String(val);
},
// 渲染静态节点
_m(index, isInFor) {
const cached = this._staticTrees || (this._staticTrees = []);
let tree = cached[index];
if (tree && !isInFor) {
return tree;
}
tree = cached[index] = this.$options.staticRenderFns[index].call(this);
tree.isStatic = true;
return tree;
},
// 渲染列表
_l(val, render) {
let ret, i, l, keys, key;
if (Array.isArray(val) || typeof val === 'string') {
ret = new Array(val.length);
for (i = 0, l = val.length; i < l; i++) {
ret[i] = render(val[i], i);
}
} else if (typeof val === 'number') {
ret = new Array(val);
for (i = 0; i < val; i++) {
ret[i] = render(i + 1, i);
}
} else if (isObject(val)) {
keys = Object.keys(val);
ret = new Array(keys.length);
for (i = 0, l = keys.length; i < l; i++) {
key = keys[i];
ret[i] = render(val[key], key, i);
}
}
return ret || [];
},
// 空VNode
_e() {
return { text: '', tag: undefined, data: undefined, children: undefined };
}
};
// 使用示例
const compiler = new VueTemplateCompiler();
// 编译模板
const template = `
<div id="app" :class="containerClass">
<h1>{{ title }}</h1>
<p v-if="showDescription">{{ description }}</p>
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index + 1 }}. {{ item.name }}
</li>
</ul>
<button @click="handleClick">点击计数: {{ count }}</button>
</div>
`;
const compiled = compiler.compile(template);
console.log('AST:', compiled.ast);
console.log('渲染函数:', compiled.render.toString());
console.log('静态渲染函数:', compiled.staticRenderFns);
// 模拟组件实例
const componentInstance = {
...helpers,
$options: { staticRenderFns: compiled.staticRenderFns },
// 组件数据
containerClass: 'app-container',
title: 'Vue模板编译示例',
showDescription: true,
description: '这是一个编译后的模板',
items: [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
],
count: 0,
handleClick() {
this.count++;
}
};
// 执行渲染函数生成VNode
const vnode = compiled.render.call(componentInstance);
console.log('生成的VNode:', vnode);
编译优化技术详解
1. 静态提升(Static Hoisting)
静态提升是Vue 3引入的重要优化技术:
// Vue 3的静态提升优化
class Vue3Compiler {
// 带静态提升的代码生成
generateWithHoisting(ast) {
const context = {
code: '',
hoists: [],
helper(name) {
return `_${name}`;
}
};
// 收集需要提升的静态节点
this.walkAST(ast, node => {
if (node.static && node.type === 'Element') {
const hoistId = context.hoists.length;
context.hoists.push(this.genElement(node));
node.hoisted = hoistId;
}
});
// 生成提升的变量声明
let code = '';
context.hoists.forEach((exp, i) => {
code += `const _hoisted_${i} = ${exp}\n`;
});
// 生成渲染函数
code += `\nfunction render() {\n`;
code += ` return ${this.genElementWithHoist(ast, context)}\n`;
code += `}`;
return code;
}
genElementWithHoist(node, context) {
if (node.hoisted !== undefined) {
return `_hoisted_${node.hoisted}`;
}
// 正常生成元素代码
return this.genElement(node);
}
}
// 编译结果对比
const template = `
<div>
<span class="static-text">这是静态文本</span>
<p>{{ dynamicText }}</p>
</div>
`;
// Vue 2编译结果(无静态提升)
function render() {
return _c('div', [
_c('span', { staticClass: 'static-text' }, [_v('这是静态文本')]),
_c('p', [_v(_s(dynamicText))])
]);
}
// Vue 3编译结果(有静态提升)
const _hoisted_1 = _c('span', { staticClass: 'static-text' }, [_v('这是静态文本')]);
function render() {
return _c('div', [
_hoisted_1, // 直接使用提升的静态节点
_c('p', [_v(_s(dynamicText))])
]);
}
2. 补丁标记(Patch Flags)
Vue 3引入了补丁标记来优化更新性能:
// 补丁标记枚举
const PatchFlags = {
TEXT: 1, // 动态文本内容
CLASS: 2, // 动态class
STYLE: 4, // 动态style
PROPS: 8, // 动态props(不包括class和style)
FULL_PROPS: 16, // 有动态key的props
HYDRATE_EVENTS: 32,// 事件监听器
STABLE_FRAGMENT: 64,// 稳定的fragment
KEYED_FRAGMENT: 128,// 有key的fragment
UNKEYED_FRAGMENT: 256,// 无key的fragment
NEED_PATCH: 512, // 需要patch的节点
DYNAMIC_SLOTS: 1024,// 动态插槽
HOISTED: -1, // 静态提升的节点
BAIL: -2 // 退出优化模式
};
// 带补丁标记的编译器
class PatchFlagCompiler {
// 分析元素的动态部分
analyzeDynamicInfo(el) {
let patchFlag = 0;
const dynamicProps = [];
// 检查动态文本
if (el.children.some(child =>
child.type === 'Interpolation'
)) {
patchFlag |= PatchFlags.TEXT;
}
// 检查动态属性
el.attrs.forEach(attr => {
if (attr.dynamic) {
if (attr.name === 'class') {
patchFlag |= PatchFlags.CLASS;
} else if (attr.name === 'style') {
patchFlag |= PatchFlags.STYLE;
} else {
patchFlag |= PatchFlags.PROPS;
dynamicProps.push(attr.name);
}
}
});
// 检查事件
if (el.events) {
patchFlag |= PatchFlags.HYDRATE_EVENTS;
}
return { patchFlag, dynamicProps };
}
// 生成带补丁标记的VNode
genVNodeWithPatchFlag(el) {
const { patchFlag, dynamicProps } = this.analyzeDynamicInfo(el);
let code = `_c('${el.tag}'`;
// 添加props
const data = this.genData(el);
if (data) {
code += `, ${data}`;
}
// 添加children
const children = this.genChildren(el);
if (children) {
code += `, ${children}`;
}
// 添加patchFlag
if (patchFlag) {
code += `, ${patchFlag}`;
// 添加动态props数组
if (dynamicProps.length) {
code += `, [${dynamicProps.map(p => `"${p}"`).join(', ')}]`;
}
}
code += ')';
return code;
}
}
// 编译结果示例
const optimizedTemplate = `
<div :id="divId" :class="divClass" @click="handleClick">
<span>静态文本</span>
<p>{{ dynamicText }}</p>
</div>
`;
// 生成的代码(带补丁标记)
function renderWithPatchFlags() {
return _c('div',
{
id: divId,
class: divClass,
onClick: handleClick
},
[
_c('span', null, ['静态文本'], -1 /* HOISTED */),
_c('p', null, [_s(dynamicText)], 1 /* TEXT */)
],
10 /* CLASS, PROPS */,
['id'] // 动态props列表
);
}
3. 内联事件处理优化
// 事件处理优化
class EventOptimizer {
// 优化内联事件处理器
optimizeInlineHandler(handler) {
// 简单的方法调用可以直接引用
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(handler)) {
return {
type: 'method',
value: handler
};
}
// 带参数的方法调用
const methodCallRE = /^([a-zA-Z_$][a-zA-Z0-9_$]*)\((.*)\)$/;
const match = handler.match(methodCallRE);
if (match) {
return {
type: 'methodWithArgs',
method: match[1],
args: match[2]
};
}
// 复杂表达式需要包装成函数
return {
type: 'expression',
value: handler
};
}
// 生成优化后的事件处理代码
genOptimizedHandler(handler) {
const optimized = this.optimizeInlineHandler(handler);
switch (optimized.type) {
case 'method':
// 直接引用方法,避免创建包装函数
return optimized.value;
case 'methodWithArgs':
// 缓存参数避免重复解析
return `function($event){${optimized.method}(${optimized.args})}`;
case 'expression':
// 复杂表达式
return `function($event){${optimized.value}}`;
}
}
}
// 优化示例
const eventTemplate = `
<div>
<button @click="handleClick">简单方法</button>
<button @click="handleClick($event, 'arg')">带参数方法</button>
<button @click="count++">内联表达式</button>
</div>
`;
// 优化后的渲染函数
function renderOptimizedEvents() {
return _c('div', [
_c('button', { on: { click: handleClick } }, ['简单方法']),
_c('button', {
on: { click: function($event) { handleClick($event, 'arg') } }
}, ['带参数方法']),
_c('button', {
on: { click: function($event) { count++ } }
}, ['内联表达式'])
]);
}
运行时编译 vs 预编译
运行时编译
// 完整版Vue(包含编译器)
import Vue from 'vue/dist/vue.js';
new Vue({
el: '#app',
template: `
<div>
<h1>{{ title }}</h1>
<p>{{ message }}</p>
</div>
`,
data: {
title: '运行时编译',
message: '模板在浏览器中编译'
}
});
// 编译过程发生在浏览器中
// 优点:灵活,可以动态编译模板
// 缺点:包体积大,首次渲染慢
预编译
// 使用单文件组件(.vue)
// MyComponent.vue
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
data() {
return {
title: '预编译',
message: '模板在构建时编译'
};
}
};
</script>
// 构建后的代码(不包含模板,只有渲染函数)
export default {
render(h) {
return h('div', [
h('h1', this.title),
h('p', this.message)
]);
},
data() {
return {
title: '预编译',
message: '模板在构建时编译'
};
}
};
// 使用运行时版本Vue(不包含编译器)
import Vue from 'vue'; // 体积更小
new Vue({
el: '#app',
render: h => h(MyComponent)
});
性能对比
// 性能测试工具
class CompilerPerformanceTester {
constructor() {
this.results = {
runtime: [],
precompiled: []
};
}
// 测试运行时编译性能
testRuntimeCompilation(template, iterations = 1000) {
const start = performance.now();
for (let i = 0; i < iterations; i++) {
const { render } = Vue.compile(template);
// 执行渲染函数
render.call({ ...testData });
}
const end = performance.now();
const duration = end - start;
this.results.runtime.push({
iterations,
duration,
average: duration / iterations
});
return duration;
}
// 测试预编译性能
testPrecompiled(renderFn, iterations = 1000) {
const start = performance.now();
for (let i = 0; i < iterations; i++) {
// 直接执行预编译的渲染函数
renderFn.call({ ...testData });
}
const end = performance.now();
const duration = end - start;
this.results.precompiled.push({
iterations,
duration,
average: duration / iterations
});
return duration;
}
// 生成报告
generateReport() {
const avgRuntime = this.average(this.results.runtime, 'duration');
const avgPrecompiled = this.average(this.results.precompiled, 'duration');
return {
runtime: {
totalTime: avgRuntime,
includesCompilation: true,
bundleSize: '~142KB (包含编译器)'
},
precompiled: {
totalTime: avgPrecompiled,
includesCompilation: false,
bundleSize: '~103KB (不含编译器)'
},
improvement: {
performance: `${((avgRuntime - avgPrecompiled) / avgRuntime * 100).toFixed(2)}%`,
bundleSize: '~28% 更小'
}
};
}
average(arr, key) {
return arr.reduce((sum, item) => sum + item[key], 0) / arr.length;
}
}
// 运行测试
const tester = new CompilerPerformanceTester();
const testTemplate = `
<div class="container">
<h1>{{ title }}</h1>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
</div>
`;
const testData = {
title: '性能测试',
items: Array(100).fill(null).map((_, i) => ({
id: i,
name: `Item ${i}`
}))
};
// 执行测试
console.log('开始性能测试...');
tester.testRuntimeCompilation(testTemplate);
tester.testPrecompiled(precompiledRenderFn);
console.log('测试报告:', tester.generateReport());
各种模板语法的编译示例
1. 指令编译
// v-if/v-else-if/v-else编译
const conditionalTemplate = `
<div>
<p v-if="score >= 90">优秀</p>
<p v-else-if="score >= 60">及格</p>
<p v-else>不及格</p>
</div>
`;
// 编译结果
function renderConditional() {
return _c('div', [
(score >= 90)
? _c('p', ['优秀'])
: (score >= 60)
? _c('p', ['及格'])
: _c('p', ['不及格'])
]);
}
// v-for编译
const listTemplate = `
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.name }}
</li>
</ul>
`;
// 编译结果
function renderList() {
return _c('ul',
_l((items), function(item, index) {
return _c('li', { key: item.id }, [
_v(_s(index) + ": " + _s(item.name))
]);
})
);
}
// v-model编译
const modelTemplate = `
<input v-model="message" />
`;
// 编译结果
function renderModel() {
return _c('input', {
domProps: { value: message },
on: {
input: function($event) {
if ($event.target.composing) return;
message = $event.target.value;
}
}
});
}
2. 插槽编译
// 插槽模板
const slotTemplate = `
<div>
<slot name="header" :title="title">
默认头部
</slot>
<slot>默认内容</slot>
<slot name="footer" />
</div>
`;
// 编译结果
function renderSlots() {
return _c('div', [
_t('header',
[_v('默认头部')],
{ title: title }
),
_t('default', [_v('默认内容')]),
_t('footer')
]);
}
// 作用域插槽编译
const scopedSlotTemplate = `
<List :items="items">
<template v-slot:item="{ item, index }">
<span>{{ index }}: {{ item.name }}</span>
</template>
</List>
`;
// 编译结果
function renderScopedSlots() {
return _c('List', {
attrs: { items: items },
scopedSlots: _u([{
key: 'item',
fn: function({ item, index }) {
return _c('span', [
_v(_s(index) + ": " + _s(item.name))
]);
}
}])
});
}
3. 动态组件编译
// 动态组件模板
const dynamicComponentTemplate = `
<component
:is="currentComponent"
v-bind="componentProps"
@custom-event="handleEvent"
/>
`;
// 编译结果
function renderDynamicComponent() {
return _c(currentComponent, {
attrs: componentProps,
on: {
'custom-event': handleEvent
}
});
}
// 异步组件编译
const asyncComponentTemplate = `
<AsyncComp v-if="showAsync" />
`;
// 编译结果
function renderAsyncComponent() {
return showAsync
? _c('AsyncComp', {
// Vue会自动处理异步组件的加载
hook: {
init(vnode) {
// 异步组件初始化逻辑
}
}
})
: _e();
}
总结
通过本文的深入剖析,我们完整地了解了Vue.js模板编译的全流程:
- 解析阶段:将模板字符串转换为AST,这是编译的基础
- 优化阶段:标记静态节点,为后续的性能优化做准备
- 代码生成阶段:将AST转换为可执行的渲染函数
我们还学习了Vue的各种编译优化技术:
- 静态提升:将静态节点提升到渲染函数外部,避免重复创建
- 补丁标记:精确标记动态部分,提升diff效率
- 事件处理优化:避免不必要的函数包装
最重要的是,我们理解了预编译相对于运行时编译的优势:
- 更小的包体积(减少约28%)
- 更好的首屏性能
- 构建时的错误检查
这些编译优化技术共同作用,使得Vue.js能够在保持开发体验的同时,提供出色的运行时性能。理解这些原理,不仅能让我们写出更高效的Vue应用,也能在遇到性能问题时快速定位和解决。
相关文章
- Diff算法深度剖析 - 了解编译后的渲染函数如何高效更新DOM
- Virtual DOM实现详解 - 理解渲染函数生成的VNode结构
- 异步更新与nextTick - 掌握Vue的批量更新机制