一、背景与概念说明
在 Vue 3 的编译阶段中,模板(template)需要被解析成 JavaScript 表达式。例如:
css
<div>{{ user.name }}</div>
会被编译为:
vbscript
_createElementVNode("div", null, _toDisplayString(user.name))
然而,模板中的表达式必须是合法的 JavaScript 语法 ,同时不能包含某些保留关键字(如 for, while, class 等)。
因此,Vue 编译器需要一个安全机制去检测表达式是否合法------这正是 validateBrowserExpression 函数的职责。
二、源码概览
vbnet
import type { SimpleExpressionNode } from './ast'
import type { TransformContext } from './transform'
import { ErrorCodes, createCompilerError } from './errors'
// 1️⃣ 定义不允许出现在表达式中的关键字
const prohibitedKeywordRE = new RegExp(
'\b' +
(
'arguments,await,break,case,catch,class,const,continue,debugger,default,' +
'delete,do,else,export,extends,finally,for,function,if,import,let,new,' +
'return,super,switch,throw,try,var,void,while,with,yield'
)
.split(',')
.join('\b|\b') +
'\b',
)
// 2️⃣ 定义用于剔除字符串字面量的正则(防止误匹配)
const stripStringRE =
/'(?:[^'\]|\.)*'|"(?:[^"\]|\.)*"|`(?:[^`\]|\.)*${|}(?:[^`\]|\.)*`|`(?:[^`\]|\.)*`/g
/**
* 3️⃣ 表达式验证函数
* 主要在浏览器端运行时编译器中调用
*/
export function validateBrowserExpression(
node: SimpleExpressionNode,
context: TransformContext,
asParams = false,
asRawStatements = false,
): void {
const exp = node.content
// ① 空表达式情况(例如 v-if="")由上层指令处理
if (!exp.trim()) {
return
}
try {
// ② 构造一个 Function 来检测表达式语法是否合法
new Function(
asRawStatements
? ` ${exp} `
: `return ${asParams ? `(${exp}) => {}` : `(${exp})`}`,
)
} catch (e: any) {
// ③ 捕获语法错误并进一步检查是否包含关键字
let message = e.message
const keywordMatch = exp
.replace(stripStringRE, '')
.match(prohibitedKeywordRE)
if (keywordMatch) {
message = `avoid using JavaScript keyword as property name: "${keywordMatch[0]}"`
}
// ④ 通过上下文的 onError 报告错误
context.onError(
createCompilerError(
ErrorCodes.X_INVALID_EXPRESSION,
node.loc,
undefined,
message,
),
)
}
}
三、原理解析
1️⃣ 关键逻辑:用 new Function() 检测表达式是否合法
javascript
new Function(`return (${exp})`)
这一技巧利用了 JavaScript 引擎本身的语法检查能力。
- 如果表达式语法错误,会直接抛出
SyntaxError。 - 如果语法合法,则不会报错,说明可安全用于运行时求值。
例如:
javascript
new Function('return (user.name)') // ✅ 通过
new Function('return (if)') // ❌ SyntaxError: Unexpected token 'if'
2️⃣ 防止关键字误用
Vue 不希望用户写出类似:
csharp
<div>{{ class }}</div>
虽然这是合法的 JS 标识符(在模板上下文中可能被误解析),但会与 JS 关键字冲突。
因此,使用正则 prohibitedKeywordRE 检测关键字出现。
注意这里的关键点:
- 先使用
stripStringRE去掉字符串字面量,防止"return"这种字符串触发误报。 - 然后再匹配关键字。
3️⃣ 错误汇报机制
通过 context.onError 统一抛出编译阶段错误:
javascript
context.onError(
createCompilerError(
ErrorCodes.X_INVALID_EXPRESSION,
node.loc,
undefined,
message,
)
)
这会被编译器统一捕获并转化为编译日志或提示信息。
四、对比分析
| 特性 | validateBrowserExpression |
Vue 服务器端编译器 (SSR) | Babel 等工具 |
|---|---|---|---|
| 检查方式 | 运行时 new Function() |
静态 AST 解析 | 语法树静态分析 |
| 运行环境 | 浏览器 | Node.js | 通用 |
| 目的 | 快速语法检测 + 安全关键字过滤 | 静态优化 + 安全执行 | 完整语言解析 |
| 性能 | 快速、轻量 | 相对较重 | 较慢但最精确 |
五、实践示例
✅ 合法表达式
css
<div>{{ user.age + 1 }}</div>
验证过程:
exp = "user.age + 1"new Function("return (user.age + 1)")✅ 无异常- 校验通过。
❌ 非法表达式(语法错误)
css
<div>{{ if user.age }}</div>
验证过程:
- 抛出
SyntaxError: Unexpected identifier - 捕获错误 → 报告
X_INVALID_EXPRESSION。
⚠️ 关键字误用
csharp
<div>{{ class }}</div>
验证过程:
-
语法层面
new Function不报错(因为class是保留字) -
但关键字匹配命中 → 提示:
vbnetavoid using JavaScript keyword as property name: "class"
六、拓展思考
- 安全性 :
new Function()在编译器中使用是安全的,因为它只执行语法检查,不执行结果。但若在运行时执行用户输入,则会有安全风险。 - 替代方案 :
在更严格的环境中,可以使用 AST 解析器(如@babel/parser)进行安全检测。 - 兼容性 :
某些浏览器中对new Function()的语法报错信息不同,因此 Vue 使用自定义错误代码 (ErrorCodes.X_INVALID_EXPRESSION) 统一处理。
七、潜在问题与优化方向
| 问题点 | 说明 | 可能优化 |
|---|---|---|
| 错误定位不精确 | 只能指出哪一条表达式出错,不能指出字符位置 | 可结合 AST 报错精确行列 |
| 关键字正则维护复杂 | 新的 JS 关键字需手动更新 | 可自动生成关键字列表 |
| 性能瓶颈 | 大量表达式时多次构造 Function 对象 |
可在编译时缓存校验结果 |
八、总结
validateBrowserExpression 是 Vue 编译器的核心安全防线之一,它通过:
new Function()检查表达式语法;- 正则匹配禁止关键字;
- 报告编译错误;
实现了轻量、快速且安全的模板表达式验证。
这一实现方案在运行时编译环境中兼顾了性能与安全性,为 Vue 模板的动态编译提供了强有力的保障。
本文部分内容借助 AI 辅助生成,并由作者整理审核。