每天一个高级前端知识 - Day 9
今日主题:编译原理在前端的应用 - 实现一个属于你自己的编译器
核心概念:前端就是编译器的世界
Babel、TypeScript、React、Vue、Sass、ESLint...本质都是编译器。理解编译原理,你就能创造属于自己的"语言"。
🔬 编译器工作流程
源代码 (字符串)
↓
【词法分析】← Token 流
↓
【语法分析】← AST (抽象语法树)
↓
【语义分析】← 类型检查/作用域
↓
【中间代码生成】← IR (中间表示)
↓
【优化】
↓
【目标代码生成】
🚀 完整实现:从0到1的编译器
场景:将简单的类JSX模板编译为原生JavaScript
// ============ 1. 词法分析器 (Lexer) ============
class Lexer {
constructor(input) {
this.input = input;
this.position = 0;
this.tokens = [];
}
tokenize() {
while (this.position < this.input.length) {
const char = this.input[this.position];
// 跳过空白字符
if (/\s/.test(char)) {
this.position++;
continue;
}
// 标签开始 <
if (char === '<') {
this.readTag();
continue;
}
// 文本内容
this.readText();
}
return this.tokens;
}
readTag() {
this.position++; // 跳过 '<'
// 检查闭合标签 </
const isClosing = this.input[this.position] === '/';
if (isClosing) this.position++;
// 读取标签名
let tagName = '';
while (this.position < this.input.length && /[a-zA-Z0-9-]/.test(this.input[this.position])) {
tagName += this.input[this.position];
this.position++;
}
// 读取属性
const attributes = this.readAttributes();
// 检查自闭合标签 />
const isSelfClosing = this.input[this.position] === '/';
if (isSelfClosing) this.position++;
// 读取结束 >
if (this.input[this.position] === '>') this.position++;
this.tokens.push({
type: isClosing ? 'CLOSE_TAG' : 'OPEN_TAG',
tagName,
attributes,
isSelfClosing
});
}
readAttributes() {
const attributes = [];
while (this.position < this.input.length &&
this.input[this.position] !== '>' &&
this.input[this.position] !== '/') {
// 跳过空白
while (/\s/.test(this.input[this.position])) this.position++;
// 读取属性名
let name = '';
while (this.position < this.input.length && /[a-zA-Z0-9-]/.test(this.input[this.position])) {
name += this.input[this.position];
this.position++;
}
// 读取属性值
let value = true;
if (this.input[this.position] === '=') {
this.position++; // 跳过 '='
// 处理引号
const quote = this.input[this.position];
if (quote === '"' || quote === "'") {
this.position++; // 跳过开始引号
value = '';
while (this.input[this.position] !== quote) {
value += this.input[this.position];
this.position++;
}
this.position++; // 跳过结束引号
} else {
// 无引号属性值
while (this.input[this.position] && !/\s/.test(this.input[this.position]) &&
this.input[this.position] !== '>' && this.input[this.position] !== '/') {
value += this.input[this.position];
this.position++;
}
}
}
attributes.push({ name, value });
}
return attributes;
}
readText() {
let text = '';
while (this.position < this.input.length && this.input[this.position] !== '<') {
text += this.input[this.position];
this.position++;
}
if (text.trim()) {
this.tokens.push({
type: 'TEXT',
value: text.trim()
});
}
}
}
// ============ 2. 语法分析器 (Parser) ============
class Parser {
constructor(tokens) {
this.tokens = tokens;
this.position = 0;
}
parse() {
const ast = {
type: 'Program',
body: this.parseChildren()
};
return ast;
}
parseChildren() {
const nodes = [];
while (this.position < this.tokens.length) {
const token = this.tokens[this.position];
if (token.type === 'OPEN_TAG') {
nodes.push(this.parseElement());
} else if (token.type === 'TEXT') {
nodes.push({
type: 'TextLiteral',
value: token.value
});
this.position++;
} else if (token.type === 'CLOSE_TAG') {
// 闭合标签由父级处理
break;
} else {
this.position++;
}
}
return nodes;
}
parseElement() {
const openToken = this.tokens[this.position++];
const element = {
type: 'Element',
tagName: openToken.tagName,
attributes: openToken.attributes,
children: []
};
// 自闭合标签
if (openToken.isSelfClosing) {
return element;
}
// 解析子元素
while (this.position < this.tokens.length) {
const nextToken = this.tokens[this.position];
if (nextToken.type === 'CLOSE_TAG' && nextToken.tagName === openToken.tagName) {
this.position++; // 跳过闭合标签
break;
}
element.children.push(...this.parseChildren());
}
return element;
}
}
// ============ 3. 代码生成器 (Generator) ============
class CodeGenerator {
constructor() {
this.indentLevel = 0;
}
generate(ast) {
return this.visit(ast);
}
visit(node) {
switch (node.type) {
case 'Program':
return this.visitProgram(node);
case 'Element':
return this.visitElement(node);
case 'TextLiteral':
return this.visitText(node);
default:
return '';
}
}
visitProgram(program) {
return program.body.map(node => this.visit(node)).join('\n');
}
visitElement(element) {
// 内置组件映射
const componentMap = {
'if': this.visitIf.bind(this),
'for': this.visitFor.bind(this),
'slot': this.visitSlot.bind(this)
};
if (componentMap[element.tagName]) {
return componentMap[element.tagName](element);
}
// 普通元素
const props = this.generateProps(element.attributes);
const children = element.children.map(child => this.visit(child)).join('\n');
return `h('${element.tagName}', ${props}, \`${children}\`)`;
}
generateProps(attributes) {
const props = {};
for (const attr of attributes) {
// 处理事件绑定 @click="handler"
if (attr.name.startsWith('@')) {
const eventName = attr.name.slice(1);
props[`on${eventName.charAt(0).toUpperCase() + eventName.slice(1)}`] = attr.value;
}
// 处理动态绑定 :src="value"
else if (attr.name.startsWith(':')) {
const propName = attr.name.slice(1);
props[propName] = attr.value;
}
// 普通属性
else {
props[attr.name] = attr.value;
}
}
return JSON.stringify(props);
}
visitIf(element) {
const conditionAttr = element.attributes.find(attr => attr.name === 'condition');
const children = element.children.map(child => this.visit(child)).join('\n');
return `\${${conditionAttr.value} ? \`${children}\` : ''}`;
}
visitFor(element) {
const eachAttr = element.attributes.find(attr => attr.name === 'each');
// each="item in items"
const [_, item, in_, list] = eachAttr.value.split(' ');
const children = element.children.map(child => this.visit(child)).join('\n');
return `\${${list}.map(${item} => \`${children}\`).join('')}`;
}
visitSlot(element) {
const nameAttr = element.attributes.find(attr => attr.name === 'name');
const name = nameAttr ? nameAttr.value : 'default';
return `\${slots['${name}'] || ''}`;
}
visitText(text) {
return text.value;
}
}
// ============ 4. 优化器 (Optimizer) ============
class Optimizer {
transform(ast) {
// 常量折叠
this.constantFolding(ast);
// 死代码消除
this.deadCodeElimination(ast);
// 内联优化
this.inlineOptimization(ast);
return ast;
}
constantFolding(node) {
// 编译期计算常量表达式
if (node.type === 'TextLiteral') {
// 识别 {{ expression }} 模式
const matches = node.value.match(/\{\{(.+?)\}\}/g);
if (matches) {
for (const match of matches) {
const expr = match.slice(2, -2);
try {
// 尝试编译期计算
const result = eval(expr);
if (typeof result !== 'function') {
node.value = node.value.replace(match, result);
}
} catch (e) {
// 保留原表达式
}
}
}
}
// 递归处理子节点
if (node.children) {
node.children.forEach(child => this.constantFolding(child));
}
}
deadCodeElimination(node) {
// 移除无法访问的代码
if (node.type === 'Element' && node.tagName === 'if') {
const conditionAttr = node.attributes.find(attr => attr.name === 'condition');
if (conditionAttr && conditionAttr.value === 'false') {
return null;
}
}
if (node.children) {
node.children = node.children
.map(child => this.deadCodeElimination(child))
.filter(Boolean);
}
return node;
}
inlineOptimization(node) {
// 内联简单的表达式
if (node.type === 'Element' && node.children.length === 1) {
const child = node.children[0];
if (child.type === 'TextLiteral' && child.value.length < 20) {
// 优化:单文本子元素直接内联到父元素
node.inlineText = child.value;
node.children = [];
}
}
if (node.children) {
node.children.forEach(child => this.inlineOptimization(child));
}
return node;
}
}
// ============ 5. 运行时环境 ============
class Runtime {
constructor(options = {}) {
this.data = options.data || {};
this.methods = options.methods || {};
this.computed = options.computed || {};
this.watch = options.watch || {};
this.computedCache = new Map();
this.watchers = new Map();
// 响应式系统
this.makeReactive(this.data);
}
makeReactive(obj) {
Object.keys(obj).forEach(key => {
let value = obj[key];
Object.defineProperty(obj, key, {
get: () => {
// 依赖收集
if (this.currentWatcher) {
if (!this.watchers.has(key)) this.watchers.set(key, new Set());
this.watchers.get(key).add(this.currentWatcher);
}
return value;
},
set: (newVal) => {
if (value !== newVal) {
value = newVal;
// 触发更新
if (this.watchers.has(key)) {
this.watchers.get(key).forEach(watcher => watcher());
}
// 触发watch回调
if (this.watch[key]) {
this.watch[key](newVal, value);
}
}
}
});
});
}
$mount(selector) {
this.el = document.querySelector(selector);
this.render();
return this;
}
render() {
// 获取计算属性值
const computedValues = {};
Object.keys(this.computed).forEach(key => {
if (!this.computedCache.has(key)) {
this.computedCache.set(key, this.computed[key].call(this));
}
computedValues[key] = this.computedCache.get(key);
});
// 渲染模板(这里应该调用编译后的渲染函数)
const html = this.template(this.data, this.methods, computedValues);
this.el.innerHTML = html;
// 绑定事件
this.bindEvents();
}
bindEvents() {
this.el.querySelectorAll('[data-on-click]').forEach(el => {
const handlerName = el.getAttribute('data-on-click');
if (this.methods[handlerName]) {
el.removeEventListener('click', this.methods[handlerName]);
el.addEventListener('click', this.methods[handlerName].bind(this));
}
});
}
}
// ============ 6. 编译器主类 ============
class TemplateCompiler {
constructor(template) {
this.template = template;
}
compile() {
// 1. 词法分析
const lexer = new Lexer(this.template);
const tokens = lexer.tokenize();
// 2. 语法分析
const parser = new Parser(tokens);
const ast = parser.parse();
// 3. 优化
const optimizer = new Optimizer();
const optimizedAST = optimizer.transform(ast);
// 4. 代码生成
const generator = new CodeGenerator();
const code = generator.generate(optimizedAST);
// 5. 可执行函数
const renderFn = new Function('data', 'methods', 'computed', 'slots', `return ${code}`);
return {
ast: optimizedAST,
code,
render: (runtime) => {
const data = runtime.data;
const methods = runtime.methods;
const computed = runtime.computedCache;
return renderFn(data, methods, computed, {});
}
};
}
}
// ============ 使用示例 ============
const template = `
<div class="app">
<if condition="showHeader">
<header>
<h1 :title="title">{{ title }}</h1>
</header>
</if>
<ul>
<for each="item in items">
<li @click="handleClick(item)">
{{ item.name }}
</li>
</for>
</ul>
<slot name="footer"></slot>
</div>
`;
const compiler = new TemplateCompiler(template);
const { render, code } = compiler.compile();
console.log('生成的代码:', code);
// 使用运行时
const app = new Runtime({
data: {
showHeader: true,
title: '我的应用',
items: [
{ name: '项目 1' },
{ name: '项目 2' },
{ name: '项目 3' }
]
},
methods: {
handleClick(item) {
console.log('点击了:', item);
}
}
});
app.template = render;
app.$mount('#app');
🎯 今日挑战
实现一个领域特定语言(DSL)编译器:
场景:设计一个表单验证DSL
// 输入DSL
const validationRule = `
rule "用户注册" {
field "username" {
required()
minLength(3)
maxLength(20)
regex(/^[a-zA-Z0-9_]+$/)
}
field "email" {
required()
email()
}
field "password" {
required()
minLength(8)
match("confirmPassword")
}
}
`;
// 你的编译器需要输出
const validator = compileValidation(validationRule);
validator.validate({ username: 'abc', email: 'test@example.com', password: '12345678' });
// 输出验证结果
提示
重点实现:
- 自定义语法解析(rule/field块)
- 验证器函数生成
- 错误信息收集
- 异步验证支持
📊 编译器优化技术对比
| 优化技术 | 效果 | 实现难度 |
|---|---|---|
| 常量折叠 | 减少运行时计算 | ⭐ |
| 死代码消除 | 减少代码体积30% | ⭐⭐ |
| 内联展开 | 提升调用性能5x | ⭐⭐⭐ |
| 循环展开 | 循环加速2-3x | ⭐⭐⭐⭐ |
| 尾调用优化 | 避免栈溢出 | ⭐⭐⭐⭐ |
| 逃逸分析 | 减少堆分配 | ⭐⭐⭐⭐⭐ |
明日预告:前端架构模式 - 微前端完全指南,从single-spa到Module Federation 2.0
💡 核心思维:编译器不仅是工具,更是"元编程"的本质——用程序写程序!