JS 动态执行字符串代码:new Function、eval 与 Node.js vm 沙箱

new Function(推荐)

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

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

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

eval 的主要区别:

  • 有独立函数作用域,不会污染外部变量
  • 可以明确传入参数,接口更清晰
  • 不能访问外部局部变量(只能访问全局)

eval(不推荐)

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

问题:

  • 和外部作用域共享变量,容易出现意外副作用
  • 引擎无法优化,性能差
  • 各种代码规范和 CSP 都禁止使用

异步代码执行

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 模块(沙箱执行)

适合在 Node 服务端执行用户提交的代码,比 eval 更安全:

const vm = require("vm");

const context = { a: 1, b: 2 };
vm.createContext(context);

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

设置超时防止死循环:

try {
  vm.runInContext("while(true){}", context, { timeout: 1000 });
} catch (e) {
  console.error("超时:", e.message); // Script execution timed out
}

执行来自数据库的函数字符串:

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

const check = new Function(`${str}; return check;`)();
console.log(check({ amount: 200 })); // true

实战:规则引擎表达式

带上下文变量执行表达式:

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

runExpression("price * count", { price: 10, count: 2 }); // 20

微信小程序:eval 和 Function 均被禁用

小程序运行环境为了安全禁止动态代码生成:

eval("1+1");          // eval is not a function
new Function("return 1"); // Function constructor is disabled

替代方案一:JSON 规则引擎

{
  "op": "and",
  "rules": [
    { "field": "amount", "operator": ">",  "value": 100 },
    { "field": "vip",    "operator": "=",  "value": true }
  ]
}
function runRule(rule, data) {
  return rule.rules.every(item => {
    switch (item.operator) {
      case ">": return data[item.field] > item.value;
      case "=": return data[item.field] === item.value;
    }
  });
}

替代方案二:globalThis 注册函数表

// 初始化时注册所有可调用函数
globalThis.actions = {
  checkVip(data)    { return data.vip; },
  checkAmount(data) { return data.amount > 100; }
};

// 运行时按名称调用
function dispatch(funcName, data) {
  return globalThis.actions[funcName]?.(data);
}

dispatch("checkAmount", { amount: 200 }); // true

globalThis 在小程序中可用(等价于浏览器的 window),但 windowdocument 在小程序里不存在。这种函数表模式是小程序低代码/规则引擎的常见做法。