如何手写简单模板字符串渲染函数?
简化版
简单模板渲染函数用于把 Hello {{ user.name }} 替换成数据里的值。实现思路是用正则匹配 {{ path }},取出路径后从数据对象里读取值,再替换回字符串。边界包括路径不存在、空值、转义、安全 XSS、是否允许表达式。面试版建议只支持属性路径,不执行任意 JS 表达式。
详细版
基础实现可以结合路径 get。
| 模板 | 数据 | 结果 |
|---|---|---|
Hi {{name}} | { name: "Tom" } | Hi Tom |
{{user.age}} | { user: { age: 18 } } | 18 |
{{missing}} | {} | 空字符串 |
function get(obj: any, path: string) {
return path.split('.').reduce((cur, key) => cur == null ? undefined : cur[key], obj)
}
function render(template: string, data: Record<string, any>) {
return template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, path) => {
const value = get(data, path)
return value == null ? '' : String(value)
})
}
手写模板时不要为了“强大”直接
eval,表达式执行会带来明显安全风险。
完整版教学
一、题目考察点
这题考正则、字符串替换、对象路径读取和安全边界。它看起来像玩具题,但能引出模板引擎的基本思想。
面试中一般不要求实现完整 Vue/Mustache,只要支持变量占位即可。
二、模板占位符怎么匹配
常见占位符是 {{ name }}。可以用正则匹配:
const reg = /\{\{\s*([\w.]+)\s*\}\}/g
含义是匹配双大括号,中间允许空格,捕获由字母数字下划线和点组成的路径。
三、如何读取路径
如果只支持 user.name,可以按点拆分。
function getByPath(obj: any, path: string) {
const keys = path.split('.')
let cur = obj
for (const key of keys) {
if (cur == null) return undefined
cur = cur[key]
}
return cur
}
不存在时返回 undefined,渲染时转成空字符串。
四、为什么不建议 eval
有些人会想支持 {{ user.age + 1 }},然后用 eval 或 new Function。这会让模板可以执行任意代码。
如果模板内容来自用户或 CMS,就可能造成 XSS 或远程代码执行风险。
面试版可以明确说:只支持安全的属性路径,不支持表达式。
五、HTML 转义问题
如果渲染结果要插入 HTML,需要考虑转义。
function escapeHtml(value: string) {
return value
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
否则用户输入 <script> 可能被当成 HTML 执行。
六、扩展能力
完整模板引擎还可能支持:
- 条件判断。
- 循环列表。
- 过滤器。
- HTML 转义和非转义输出。
- 编译缓存。
| 能力 | 面试基础版 | 完整模板引擎 |
|---|---|---|
| 变量替换 | 支持 | 支持 |
| 表达式 | 不建议 | 受控支持 |
| 安全转义 | 可加 | 必须设计 |
| 编译优化 | 不需要 | 常见 |
七、常见误区与追问
- 误区:模板渲染直接 replace 就完全够。 还要考虑路径不存在、转义、安全和重复占位符。
- 误区:用 eval 支持表达式更高级。 任意代码执行风险很高,面试中应主动规避。
- 误区:null 应该渲染成字符串 null。 多数模板会把 null/undefined 渲染为空字符串,具体要约定。
- 追问:如何防 XSS? 插入 HTML 前对动态值做 HTML 转义,或使用安全 DOM API。
- 追问:如何提高重复渲染性能? 可以先编译模板为 token 或函数,并缓存编译结果。
- 追问:如何支持数组路径? 复用更完整的 getByPath,支持
items[0].name。
八、加强记忆
模板渲染记成“匹配占位符、读取路径、替换字符串”。工程答案要补一句:默认只支持安全路径,不执行任意表达式,插入 HTML 要转义。