JavaScript 动态执行代码:new Function、eval 与 Node.js vm 沙箱

三种方式对比

方式能访问外部作用域安全性推荐场景
eval()几乎不推荐
new Function()浏览器/Node 通用
vm.runInContext()否(隔离)Node.js 执行用户代码

new Function(推荐)

// 无参数
const fn = new Function("return 1 + 2");
console.log(fn()); // 3

// 带参数
const add = new Function("a", "b", "return a + b");
console.log(add(1, 2)); // 3

// 多行代码
const code = `
    const x = 10;
    return x * 2;
`;
console.log(new Function(code)()); // 20

new Function 创建的函数只能访问全局作用域,不能访问创建它时的局部变量——这是它比 eval 更安全的原因。

eval(不推荐)

const result = eval("1 + 2");  // 3

eval 可以访问当前闭包内的变量,是潜在的代码注入入口,大多数代码规范禁止使用。

注入上下文:with(data)

在表单引擎、规则配置等场景中,需要让动态代码访问对象属性:

function runExpression(expr, data) {
    return new Function("data", `with(data){ return ${expr} }`)(data);
}

runExpression("price * count", { price: 10, count: 2 }); // 20
runExpression("amount > 100", { amount: 200 }); // true

with(data) 把 data 的属性提升为词法作用域,表达式中可以直接使用属性名。

执行用户脚本(带函数体)

从数据库加载 JS 函数字符串并执行:

const scriptStr = `
function check(order) {
    return order.amount > 100;
}
`;

// 提取函数体,传入参数执行
const fn = new Function("order", `
    ${scriptStr}
    return check(order);
`);

console.log(fn({ amount: 200 })); // true

异步版本:AsyncFunction

// 获取异步函数构造器
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;

const code = `
    const res = await Promise.resolve(123);
    return res;
`;

const fn = new AsyncFunction(code);
fn().then(console.log); // 123

Node.js vm 沙箱(服务端执行用户代码)

vm 模块提供真正的上下文隔离,适合服务端运行不受信任的代码:

const vm = require("vm");

// 创建隔离上下文
const context = { a: 1, b: 2, result: 0 };
vm.createContext(context);

vm.runInContext("result = a + b", context);
console.log(context.result); // 3

设置超时,防止死循环:

try {
    vm.runInContext(
        "while(true){}",
        context,
        { timeout: 1000 }  // 1秒超时
    );
} catch (e) {
    console.log("超时:", e.message);
}

完整执行流程

function safeExec(code, data, timeoutMs = 500) {
    const ctx = { ...data, __result: undefined };
    vm.createContext(ctx);
    vm.runInContext(
        `__result = (function(){ ${code} })()`,
        ctx,
        { timeout: timeoutMs }
    );
    return ctx.__result;
}

safeExec("return a * b", { a: 3, b: 4 }); // 12

vm 模块的局限

vm 不是完全安全的沙箱,能被构造特定代码逃逸(prototype chain escape)。对安全要求更高的场景,使用 vm2 库(已停止维护)或 isolated-vm(推荐):

import ivm from "isolated-vm";

const isolate = new ivm.Isolate({ memoryLimit: 64 });
const ctx = await isolate.createContext();
const result = await ctx.eval("1 + 2");