使用isolated-vm设置含自定义对象实例的全局变量问题求助
isolated-vm设置含自定义对象实例的全局变量问题求助
嗨,我明白你遇到的问题了——用ExternalCopy把自定义的VariablesWrapper实例传到isolated-vm的隔离上下文后,访问属性返回undefined,但直接传普通对象就正常。这背后的原因其实很关键:
ExternalCopy依赖的是结构化克隆算法,这种算法只会复制对象的数据属性,不会保留你通过Object.defineProperty定义的访问器属性(getter/setter)。你的VariablesWrapper实例本身并没有key1、key2这些实际的数据属性,只有对应的访问器,所以当它被复制到隔离上下文后,就变成了一个空对象,自然访问属性会返回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




