比 eval 和 iframe 更强的新一代 JavaScript 沙箱!
今天我们来看一个进入 statge3 的新的 JavaScript 提案:ShadowRealm API。
JavaScript 的运行环境
领域(realm),这个词比较抽象,其实就代表了一个 JavaScript 独立的运行环境,里面有独立的变量作用域。
比如下面的代码:
<body>
??<iframe>
??</iframe>
??<script>
????const?win?=?frames[0].window;
????console.assert(win.globalThis?!==?globalThis);?//?true
????console.assert(win.Array?!==?Array);?//?true
??</script>
</body>
每个 iframe 都有一个独立的运行环境,document 的全局对象不同于 iframe 的全局对象,类似的,全局对象上的 Array 肯定也不同。
ShadowRealm API
ShadowRealm API 是一个新的 JavaScript 提案,它允许一个 JS 运行时创建多个高度隔离的 JS 运行环境(realm),每个 realm 具有独立的全局对象和内建对象。
ShadowRealm 具有下面的类型签名:
declare?class?ShadowRealm?{
??constructor();
??evaluate(sourceText:?string):?PrimitiveValueOrCallable;
??importValue(specifier:?string,?bindingName:?string):?Promise<PrimitiveValueOrCallable>;
}
每个 ShadowRealm 实例都有自己独立的运行环境,它提供了两种方法让我们来执行运行环境中的代码:
.evaluate():同步执行代码字符串,类似eval()。.importValue():返回一个Promise对象,异步执行代码字符串。
shadowRealm.evaluate()
.evaluate() 的类型签名:
evaluate(sourceText:?string):?PrimitiveValueOrCallable;
.evaluate() 的工作原理很像 eval():
const?sr?=?new?ShadowRealm();
console.assert(
??sr.evaluate(`'ab'?+?'cd'`)?===?'abcd'
);
但是与 eval() 不同的是,代码是在 .evaluate() 的独立运行环境中执行的:
globalThis.realm?=?'incubator?realm';
const?sr?=?new?ShadowRealm();
sr.evaluate(`globalThis.realm?=?'ConardLi?realm'`);
console.assert(
??sr.evaluate(`globalThis.realm`)?===?'ConardLi?realm'
);
如果 .evaluate() 返回一个函数,为了方便在外部调用这个函数会被包装,然后在 ShadowRealm 中运行:
globalThis.realm?=?'incubator?realm';
const?sr?=?new?ShadowRealm();
sr.evaluate(`globalThis.realm?=?'ConardLi?realm'`);
const?wrappedFunc?=?sr.evaluate(`()?=>?globalThis.realm`);
console.assert(wrappedFunc()?===?'ConardLi?realm');
每当一个值传入 ShadowRealm 时,它必须是原始类型或者可以被调用的。否则会抛出异常:
>?new?ShadowRealm().evaluate('[]')
TypeError:?value?passing?between?realms?must?be?callable?or?primitive
shadowRealm.importValue()
.importValue() 的类型签名:
importValue(specifier:?string,?bindingName:?string):?Promise<PrimitiveValueOrCallable>;
你可以直接导入一个外部的模块,异步执行并返回一个 Promise,用法:
//?main.js
const?sr?=?new?ShadowRealm();
const?wrappedSum?=?await?sr.importValue('./my-module.js',?'sum');
console.assert(wrappedSum('hi',?'?',?'folks',?'!')?===?'hi?ConardLi!');
//?my-module.js
export?function?sum(...values)?{
??return?values.reduce((prev,?value)?=>?prev?+?value);
}
与 .evaluate() 一样,传入 ShadowRealms 的值(包括参数和跨环境函数调用的结果)必须是原始的或可调用的。
ShadowRealms 可以用来做什么?
在
Web IDE或Web绘图应用等程序中运行插件等第三方代码。在
ShadowRealms中创建一个编程环境,运行用户代码。服务器可以在
ShadowRealms中运行第三方代码。在 ShadowRealms 中可以运行测试,这样外部的JS执行环境不会受到影响,并且每个套件都可以在新环境中启动(这有助于提高可复用性)。
网页抓取(从网页中提取数据)和网页应用测试等可以在
ShadowRealms中运行。
与其他方案对比
eval()和Function
ShadowRealms 与 eval() 和 Function 很像,但比它们俩都好一点:我们可以创建新的JS运行环境并在其中执行代码,这可以保护外部的JS运行环境不受代码执行的操作的影响。
Web Workers
Web Worker 是一个比 ShadowRealms 更强大的隔离机制。其中的代码运行在独立的进程中,通信是异步的。
但是,当我们想要做一些更轻量级的操作时,ShadowRealms 是一个很好的选择。它的算法可以同步计算,更便捷,而且全局数据管理更自由。
iframe
前面我们已经提到了,每个 iframe 都有自己的运行环境,我们可以在里面同步执行代码。
<body>
??<iframe>
??</iframe>
??<script>
????globalThis.realm?=?'incubator';
????const?iframeRealm?=?frames[0].window;
????iframeRealm.globalThis.realm?=?'ConardLi';
????console.log(iframeRealm.eval('globalThis.realm'));?//?'ConardLi'
??</script>
</body>
与 ShadowRealms 相比,还是有以下缺点:
只能在浏览器中使用 iframe;需要向 DOM添加一个iframe以对其进行初始化;每个 iframe环境都包含完整的 DOM,这在一些场景下限制了自定义的灵活度;默认情况下,对象是可以跨环境的,这意味着需要额外的工作来确保代码安全。
Node.js 上的 vm 模块
Node.js 的 vm 模块与 ShadowRealm API 类似,但具有更多功能:缓存 JavaScript 引擎、拦截 import() 等等。但它唯一的缺点就是不能跨平台,只能在 Node.js 环境下使用。
用法示例:在 ShadowRealms 中运行测试
下面我们来看个在 ShadowRealms 中运行测试的小 Demo,测试库收集通过 test() 指定的测试,并允许我们通过 runTests() 运行它们:
//?test-lib.js
const?testDescs?=?[];
export?function?test(description,?callback)?{
??testDescs.push({description,?callback});
}
export?function?runTests()?{
??const?testResults?=?[];
??for?(const?testDesc?of?testDescs)?{
????try?{
??????testDesc.callback();
??????testResults.push(`${testDesc.description}:?OK\n`);
????}?catch?(err)?{
??????testResults.push(`${testDesc.description}:?${err}\n`);
????}
??}
??return?testResults.join('');
}
使用库来指定测试:
//?my-test.js
import?{test}?from?'./test-lib.js';
import?*?as?assert?from?'./assertions.js';
test('succeeds',?()?=>?{
??assert.equal(3,?3);
});
test('fails',?()?=>?{
??assert.equal(1,?3);
});
export?default?true;
在下一个示例中,我们动态加载 my-test.js 模块来收集然后运行测试。
唉,目前还没有办法在不导入任何东西的情况下加载模块。
这就是为什么在前面示例的最后一行中有一个默认导出。我们使用 ShadowRealm .importvalue() 方法导入 default export 。
//?test-runner.js
async?function?runTestModule(moduleSpecifier)?{
??const?sr?=?new?ShadowRealm();
??await?sr.importValue(moduleSpecifier,?'default');
??const?runTests?=?await?sr.importValue('./test-lib.js',?'runTests');
??const?result?=?runTests();
??console.log(result);
}
await?runTestModule('./my-test.js');
在 ShadowRealms 中运行 Web 应用
jsdom 库创建了一个封装的浏览器环境,可以用来测试 Web 应用、从 HTML 中提取数据等。它目前使用的是 Node.js vm 模块,未来可能会更新为使用 ShadowRealms(后者的好处是可以跨平台,而 vm 目前只支持 Node.js)。
参考
- EOF -
1、微信动不动就占用几十 GB!UP 主深扒:同一个文件居然存 10 份
关注「程序员的那些事」加星标,不错过圈内事
点赞和在看就是最大的支持?
关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
关注网络尖刀微信公众号随时掌握互联网精彩
- 1 习近平将发表二〇二六年新年贺词 7904141
- 2 2026年国补政策来了 7808738
- 3 东部战区:开火!开火!全部命中! 7712893
- 4 2026年这些民生政策将惠及百姓 7616985
- 5 小学食堂米线过期2.5小时被罚5万 7519709
- 6 解放军喊话驱离台军 原声曝光 7428214
- 7 为博流量直播踩烈士陵墓?绝不姑息 7327605
- 8 每月最高800元!多地发放养老消费券 7238391
- 9 数字人民币升级 1月1日起将计付利息 7141831
- 10 2026年1月1日起 一批新规将施行 7040675








程序员的那些事
