JavaScript模板引擎核心是解析、编译、渲染三步,基础用with+new Function,进阶用词法分析+AST,需沙箱隔离与安全语法设计。
JavaScript 实现模板引擎的核心在于:将模板字符串与数据对象结合,通过解析、编译、渲染三步生成最终 HTML(或任意文本)。自定义语法的关键是控制“占位符识别”和“表达式求值”逻辑,不依赖正则硬匹配,而是用更健壮的解析方式。
最简实现利用 with 作用域和动态函数构造,适合学习底层逻辑(注意:with 在严格模式下禁用,生产环境建议用 Proxy 或作用域对象显式访问):
function compile(template, data) {
// 把 {{ name }} 替换成 data.name,支持简单点号路径如 {{ user.profile.name }}
const code = template
.replace(/{{\s*([\s\S]+?)\s*}}/g, (_, exp) => {
// 简单安全化:只允许字母、数字、点、括号、空格、运算符(避免执行任意代码)
if (/^[a-zA-Z0-9_.\[\]\(\)\s+\-*/%&|^!~?:]+$/.test(exp)) {
return `'+(${exp})+'`;
}
return "''";
})
.replace(/\\n/g, '\\n');
const fn = new Function('data', `with(data){return \`${code}\`;}`);
return fn(data);
}
// 使用
const html = compile("Hello {{ name }}, age:
{{ age + 1 }}", { name: "Alice", age: 25 });
// → "Hello Alice, age: 26"
真正可控、可扩展的自定义语法需跳过正则直替,转为分词(tokenize)→ 构建抽象语法树(AST)→ 生成渲染函数。例如支持:
{{ if user.admin }}Admin{{ /if }}{{ each items as item index }}- {{ item.name }}
{{ /each }}{{ include 'header.html' }}关键步骤:
{{ each list }} → { type: 'OPEN_TAG', name: 'each', args: ['list'] })if 节点 → `if (${cond}) { ${bodyCode} }`),最后用 new Function 编译用户输入的模板不可信,必须隔离执行环境:
data 包一层 Proxy,拦截 __proto__、constructor 等敏感属性data[key] 形式,禁止 this.xxx 或全局变量引用setTimeout + throw 中断长循环(需在生成代码中注入检查)with:显式解构 const { name, age } = data,或用 Object.assign({ }, data) 创建干净上下文自定义语法不是越像 Vue 越好,而是要便于 tokenizer 切分且无歧义:
{{ / }},避免 {% 和 {{ 混用(增加状态复杂度){{#if}} 和 {{/if}} 比 {{if}} + {{endif}} 更易配对{{ formatPrice(price) }}
{{ @partial 'sidebar' }} 加载子模板,或 {{ $index }} 注入内置变量