ShadowRealm 让浏览器终于有“隔离房间”了,前端工程师该知道这门新规范了
ShadowRealm 让浏览器终于有“隔离房间”了,前端工程师该知道这门新规范了
导语
你有没有在一个页面里同时加载了 React、Vue、jQuery,结果某个库随手往 window 上塞了个变量,另一个库恰好重名,页面就崩了?或者你负责的是一个第三方 SDK 页面,明明只是插入一段脚本,却总担心污染宿主环境?这些问题,浏览器原生终于要给出一个正经答案了:ShadowRealm API。它能让一段 JavaScript 代码运行在“另一个全局环境”里,和页面本身互不干扰,而且不需要 iframe、不需要 Web Worker,更不需要构建工具打包。问题是,这门规范现在走到哪了?前端工程师现在能不能用?
正文
问题:全局污染为什么 20 年都没根治?
JavaScript 的“单线程”经常被说成缺点,但真正让前端工程师头疼的不是单线程,而是全局作用域太容易共享。
一个页面上,主脚本、第三方统计、广告代码、旧版 jQuery 插件,全挤在同一个 window 里。只要有一行代码写错:
// 某 SDK 悄悄改了 Array
window.Array = function () { /* ... */ };
整个页面的数组逻辑都可能炸掉。 historically,浏览器给了我们几个隔离手段:
- iframe:不同 origin 的 iframe 有独立全局对象,但同源 iframe 仍然共享,而且跨页面通信要用
postMessage,很重。 - Web Worker:有独立线程和全局对象,但不能访问 DOM,也不能和主线程共享可变对象。
- 模块化(ESM):
import/export解决了文件级作用域,但所有模块仍然共享同一个globalThis,window.Array还是同一个东西。
这些方案要么太重,要么不够隔离,要么和 DOM 绝缘。前端工程师需要一个轻量、同线程、隔离全局环境的方案。
方案:ShadowRealm —— 给 JS 代码一个“独立房间”
TC39 的 ShadowRealm 提案就是为了解决这个问题。它现在已经走到 Stage 2.7,距离进入标准已经不远。
核心思路很简单: 创建一个 ShadowRealm 实例,里面的代码有自己独立的全局对象和内建对象,和外部互不污染。但它不像 iframe 或 Worker,代码仍然在同一个线程里执行,所以可以直接操作 DOM,也可以直接返回结果,不需要序列化。
// 主页面里的代码
const shadow = new ShadowRealm();
// 在 ShadowRealm 里执行代码,外面看不到这里的全局变量
shadow.evaluate(`
function secret() {
return "这个函数只在 ShadowRealm 里存在";
}
`);
// 外部 globalThis 里没有 secret
console.log(typeof secret); // undefined
// 但可以从 ShadowRealm 里把函数“借”出来
const secretFn = shadow.evaluate('secret');
console.log(secretFn()); // "这个函数只在 ShadowRealm 里存在"
注意,secretFn 是一个从 ShadowRealm 返回的引用,它仍然带着 ShadowRealm 的作用域,但你在外部可以正常调用它。也就是说,隔离边界是可穿越的,但默认是封闭的。
另一个核心方法是 importValue:
// 主页面
const shadow = new ShadowRealm();
// 从 ShadowRealm 的模块系统里导入值
const mod = shadow.importValue('./utils.js', 'formatDate');
// 然后在主线程里调用
console.log(mod(new Date()));
这意味着你可以在 ShadowRealm 里跑一套完全独立的模块系统,和主应用的 node_modules 互不干扰。
代码与数据:ShadowRealm 的真实能力边界
能力 1:隔离内建对象,不只是隔离变量
不是只有你自己写的函数会被隔离,Array、Promise、Error 这些内建对象也是隔离的:
const shadow = new ShadowRealm();
// ShadowRealm 里的 Array 和外面不是同一个
shadow.evaluate(`
window.customArray = Array;
`);
console.log(window.customArray === Array); // false
这意味着如果你想在 ShadowRealm 里跑一段不受信任的第三方代码,它没法偷偷篡改主应用的 Array.prototype。
能力 2:仍然能操作 DOM,不需要 Worker
这是 ShadowRealm 和 Web Worker 最大的区别:
const shadow = new ShadowRealm();
shadow.evaluate(`
// 这段代码在 ShadowRealm 里运行
// 但它能访问同一个 DOM
const btn = document.createElement('button');
btn.textContent = '来自 ShadowRealm 的按钮';
document.body.appendChild(btn);
`);
因为代码还在主线程,所以直接操作 DOM 没问题。这让它非常适合跑第三方 SDK、埋点脚本、广告代码,又不用担心它们污染全局。
能力 3:不能共享可变对象的引用
虽然可以“借出”函数,但你不能把一个在 ShadowRealm 里创建的对象直接拿到外面改:
const shadow = new ShadowRealm();
const obj = shadow.evaluate('({ a: 1 })');
obj.a = 2; // 可以改属性
// 但这个对象仍然是 ShadowRealm 内部实例
这个边界设计是刻意的:ShadowRealm 提供的是完整性边界(integrity boundary),不是安全边界(security boundary)。也就是说,代码不能直接篡改外部环境,但可以通过返回值和外部通信。如果你需要真正的安全沙箱,还需要额外加一层能力检查。
结论:前端工程师现在该怎么做?
ShadowRealm 不是“未来可用”,而是“快要能用了”。
TC39 提案目前处于 Stage 2.7,这意味着:
- 规范文本已经基本稳定,浏览器厂商可以开始实现了。
- Chrome 团队已经在推进,预计在接下来的几个版本里会开始 flag 试验。
- 现在可以写 polyfill/实验性代码,但不要在生产环境直接依赖。
对于前端工程师,我的判断是:
- 如果你的场景是跑第三方 SDK/埋点/广告代码,现在可以用 iframe + postMessage 做隔离,等 ShadowRealm 稳定后迁移成本很低。
- 如果你想做代码沙箱/在线编辑器/低代码平台,现在开始关注 ShadowRealm,它比
new Function+with这种老方案干净得多。 - 如果你在做框架/工具链,可以开始设计基于 ShadowRealm 的插件隔离机制,这会成为下一个十年前端架构的基础设施。
收尾:给你一个可落地的下一步
判断标准:当你的项目里有超过 3 个第三方脚本需要同时加载,且其中至少 1 个出现过全局变量冲突时,就应该认真考虑 ShadowRealm。
可落地的一步是:现在开始在你的实验项目里写一个 runInShadow(code) 工具函数,提前熟悉 API 语义。等浏览器实现落地,你的迁移成本会非常低。
JavaScript 的单线程本质不会变,但 ShadowRealm 至少告诉我们:同一把钥匙开的门里,终于可以有几间独立的房间了。
评论区
登录后可评论。













