本文介绍一种安全、可控的方式,利用 `function` 构造函数动态创建具有指定变量作用域的执行环境,避免字符串拼接注入和污染全局作用域,实现类似 `eval(script).call(context)` 的效果。
在 JavaScript 中,eval() 默认在当前作用域(通常是全局或函数局部)执行代码,但它无法直接接收一个对象作为作用域上下文——eval().call(context) 是无效的,因为 eval 是固有函数,其作用域由调用位置决定,而非 .call() 指定。
然而,我们可以通过 Function 构造函数巧妙绕过这一限制。Function 创建的函数拥有自己的词法作用域,且其参数名会成为函数体内的绑定变量名。结合 eval 在该函数体内执行,即可让 script 字符串自然访问这些参数变量。
function evalWithContext(script, context) {
// 提取变量名(作为参数)和变量值(作为实参)
const keys = Object.keys(context);
const values = Object.values(context);
// 构造函数:参数为所有变量名 + 'script',函数体中先屏蔽 script 参数干扰,再 eval
const evaluator = Function(
...keys,
'script',
'return eval("script = undefined;" + script);'
);
// 调用时传入对应值 + script 字符串
return evaluator(...values, script);
}
// 使用示例
const userVars = [
{ name: 'x', value: 10 },
{ name: 'y', value: 20 },
{ name: 'z', value: 5 }
];
const context = Object.fromEntries(userVars.map(({ name, value }) => [name, value]));
console.log(evalWithContext('x + y * z', context)); // → 110
console.log(evalWithContext('x > y', context)); // → false
r 等真正隔离环境。| 方案 | 安全性 | 作用域隔离 | 可维护性 | 备注 |
|---|---|---|---|---|
| eval('var ' + name + '=' + JSON.stringify(val)) | ❌ 高风险(XSS/语法错误) | ❌ 全局污染 | ❌ 易出错 | JSON.stringify 仅保值,不保类型(如函数、正则丢失) |
| 直接赋值到 window 后全局 eval | ❌ 污染全局、竞态风险 | ❌ 无隔离 | ❌ 不适用于多实例 | delete window.x 不可靠,且非严格模式下失败 |
| with(context) { eval(script) } | ❌ 已废弃、禁用严格模式 | ⚠️ 表面隔离,实际影响作用域链 | ❌ 不推荐、ES5+ 不兼容 | V8 等引擎已禁用 with 优化,性能差且语义模糊 |
Function 构造函数 + 参数绑定 + eval 封装,是目前在标准 JavaScript 环境中实现「动态变量作用域内安全求值」最简洁、可靠、无需额外依赖的方案。它将变量名显式声明为函数参数,天然获得词法作用域保护,同时规避了字符串拼接与全局污染两大陷阱。只要确保 script 输入可信(或进一步沙箱化),即可放心用于配置表达式、简易公式引擎、低风险 DSL 执行等场景。