模板编译是将模板字符串一次性编译为可执行JavaScript函数,而非简单字符串替换;其核心流程包括词法分析(状态机切分TEXT/SCRIPTlet Token)、AST构建、作用域管理及代码生成,最终通过new Function动态创建render函数,兼顾性能、安全与逻辑完整性。

模板编译不是字符串拼接,而是生成可执行函数
直接用 replace() 或 innerHTML 插值,会丢失控制逻辑、无法复用、难调试,也容易引入 XSS。真正的模板引擎(如 EJS、Nunjucks、或手写的简易版)在首次加载时就把模板字符串编译成一个 JavaScript 函数,比如:new Function('data', 'return `<div>${data.title}</div>`')。
这个函数接收数据对象,返回 HTML 字符串。关键点在于:编译只做一次,后续调用纯执行,性能高;且表达式和语句(if、for)被转为合法 JS 语法,不是简单替换。
- 模板中
→ 编译后变成if (data.user) { -
→ 转为data.name(注意是未转义,才原样输出) - 空格、换行、注释等非逻辑内容保留,但不会参与 JS 执行
词法分析阶段必须区分文本块与逻辑块
模板字符串里混着 HTML 文本和 JS 表达式,Tokenizer 的任务就是切出两类 Token:TEXT 和 SCRIPTlet(即 或 )。这不能靠正则全局匹配——遇到嵌套引号、多行字符串、注释就崩。
正确做法是状态机驱动:初始为文本态,遇到 切到脚本态;在脚本态中识别 <code>%> 退出;中间还要处理引号配对、转义字符(如 \')、JS 注释(//、/* */)是否跨行等边界。
立即学习“前端免费学习笔记(深入)”;
- 错误示例:
若没处理单引号内嵌套,会导致编译生成非法 JS - 常见坑:把
当作普通文本,实际应跳过不输出 - ES6 模板字面量(
`)在编译阶段不能直接用,因为引擎本身运行在 ES5 环境或需兼容旧浏览器
Tree Construction 不发生在浏览器端,而是在内存中构建 AST
和 HTML 解析器不同,模板引擎不走 DOM 构建流程,它构建的是抽象语法树(AST),节点类型包括 TextNode、IfNode、EachNode、ExpressionNode。这个 AST 是中间产物,用来生成最终的函数体代码。
例如模板:<ul> { %><li>
</ul>,AST 会明确记录循环体、变量作用域、表达式位置,再据此拼出类似 let html = `<ul>`; data.users.forEach(u => { html += `<li>${u.name}</li>` }); html += `</ul>`; 的代码。
- 作用域管理很关键:子模板(include)传入的局部变量不能污染父作用域
- AST 需支持 source map 映射,否则报错时无法定位原始模板行号
- 不建议在运行时动态生成 AST,应预编译(build-time)或缓存已编译函数
HTML 结构完整性由开发者负责,引擎不校验
模板引擎不会像浏览器那样自动补全 <p></p>、闭合 <li>、修正嵌套错误。它只保证你写的模板能编译成合法 JS 并执行——至于生成的 HTML 是否符合规范、能否被浏览器正确解析,那是你的事。
这意味着:<div><span>text 这种缺闭合标签的写法,在模板里完全合法,编译后就是原样输出;但浏览器解析时会触发容错机制,可能插入意外的 <code>



















