You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用isolated-vm设置含自定义对象实例的全局变量问题求助

isolated-vm设置含自定义对象实例的全局变量问题求助

嗨,我明白你遇到的问题了——用ExternalCopy把自定义的VariablesWrapper实例传到isolated-vm的隔离上下文后,访问属性返回undefined,但直接传普通对象就正常。这背后的原因其实很关键:

ExternalCopy依赖的是结构化克隆算法,这种算法只会复制对象的数据属性,不会保留你通过Object.defineProperty定义的访问器属性(getter/setter)。你的VariablesWrapper实例本身并没有key1key2这些实际的数据属性,只有对应的访问器,所以当它被复制到隔离上下文后,就变成了一个空对象,自然访问属性会返回undefined

那该怎么解决呢?核心思路是:在隔离上下文内部构建带有访问器逻辑的$var对象,而不是从外部复制已经定义好访问器的实例。下面给你两种可行的实现方案:

方案一:手动为每个属性定义访问器

这种方式更贴近你原本的VariablesWrapper思路,在隔离上下文里逐个给$var对象添加getter和setter:

const isolatedVm = require('isolated-vm');

const myRecord = {
  key1: "a value",
  key2: new Error("an error")
};

const isolate = new isolatedVm.Isolate({ memoryLimit: 64 });
const context = isolate.createContextSync();
const jail = context.global;

// 常规操作:让隔离上下文的global指向自身
jail.setSync("global", jail.derefInto());

// 创建空的$var对象
const $var = jail.getSync("Object").newSync();

// 遍历记录,为每个属性添加访问器
for (const [key, originalValue] of Object.entries(myRecord)) {
  // 将原始值复制到隔离上下文(包括Error对象)
  const copiedValue = new isolatedVm.ExternalCopy(originalValue).copyInto(context);

  // 定义getter:检查值是否为Error,是则抛出,否则返回
  const getter = isolate.compileFunctionSync(`(value) => {
    if (value instanceof Error) {
      throw value;
    }
    return value;
  }`);

  // 定义setter:直接抛出修改禁止的错误
  const setter = isolate.compileFunctionSync(`() => {
    throw new Error("You cannot change a variable value.");
  }`);

  // 在$var上绑定属性的访问器
  jail.getSync("Object").definePropertySync($var, key, {
    get: getter.bindSync(context, copiedValue),
    set: setter.bindSync(context),
    enumerable: true // 可选:让属性可被枚举
  });
}

// 将$var设为全局变量
jail.setSync("$var", $var);

// 测试验证
const script1 = isolate.compileScriptSync("$var.key1");
console.log(script1.runSync(context)); // 输出:a value

const script2 = isolate.compileScriptSync("$var.key2");
try {
  script2.runSync(context);
} catch (err) {
  console.log(err.message); // 输出:an error
}

const script3 = isolate.compileScriptSync("$var.key1 = 'new value'");
try {
  script3.runSync(context);
} catch (err) {
  console.log(err.message); // 输出:You cannot change a variable value.
}

方案二:使用Proxy拦截属性访问

这种方式更简洁,利用ES Proxy来统一处理所有属性的访问和修改操作:

const isolatedVm = require('isolated-vm');

const myRecord = {
  key1: "a value",
  key2: new Error("an error")
};

const isolate = new isolatedVm.Isolate({ memoryLimit: 64 });
const context = isolate.createContextSync();
const jail = context.global;

// 常规操作:设置global指向自身
jail.setSync("global", jail.derefInto());

// 将原始记录复制到隔离上下文
const copiedRecord = new isolatedVm.ExternalCopy(myRecord).copyInto(context);

// 创建Proxy的处理对象,定义拦截逻辑
const proxyHandler = jail.getSync("Object").newSync();
proxyHandler.setSync("get", isolate.compileFunctionSync(`(target, prop) => {
  const value = target[prop];
  if (value instanceof Error) {
    throw value;
  }
  return value;
}`));
proxyHandler.setSync("set", isolate.compileFunctionSync(`() => {
  throw new Error("You cannot change a variable value.");
}`));

// 创建Proxy对象作为$var
const $var = jail.getSync("Proxy").newSync(copiedRecord, proxyHandler);

// 绑定到全局
jail.setSync("$var", $var);

// 测试验证
const script1 = isolate.compileScriptSync("$var.key1");
console.log(script1.runSync(context)); // 输出:a value

const script2 = isolate.compileScriptSync("$var.key2");
try {
  script2.runSync(context);
} catch (err) {
  console.log(err.message); // 输出:an error
}

const script3 = isolate.compileScriptSync("$var.key1 = 'test'");
try {
  script3.runSync(context);
} catch (err) {
  console.log(err.message); // 输出:You cannot change a variable value.
}

这两种方案都能完美实现你的需求:$var是只读的,访问Error类型的属性会抛出错误,访问字符串则返回对应值。

备注:内容来源于stack exchange,提问作者earendil06

火山引擎 最新活动