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 解决了文件级作用域,但所有模块仍然共享同一个 globalThiswindow.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:隔离内建对象,不只是隔离变量

不是只有你自己写的函数会被隔离,ArrayPromiseError 这些内建对象也是隔离的:


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,这意味着:

  1. 规范文本已经基本稳定,浏览器厂商可以开始实现了。
  2. Chrome 团队已经在推进,预计在接下来的几个版本里会开始 flag 试验。
  3. 现在可以写 polyfill/实验性代码,但不要在生产环境直接依赖。

对于前端工程师,我的判断是:

  • 如果你的场景是跑第三方 SDK/埋点/广告代码,现在可以用 iframe + postMessage 做隔离,等 ShadowRealm 稳定后迁移成本很低。
  • 如果你想做代码沙箱/在线编辑器/低代码平台,现在开始关注 ShadowRealm,它比 new Function + with 这种老方案干净得多。
  • 如果你在做框架/工具链,可以开始设计基于 ShadowRealm 的插件隔离机制,这会成为下一个十年前端架构的基础设施。

收尾:给你一个可落地的下一步

判断标准:当你的项目里有超过 3 个第三方脚本需要同时加载,且其中至少 1 个出现过全局变量冲突时,就应该认真考虑 ShadowRealm。

可落地的一步是:现在开始在你的实验项目里写一个 runInShadow(code) 工具函数,提前熟悉 API 语义。等浏览器实现落地,你的迁移成本会非常低。

JavaScript 的单线程本质不会变,但 ShadowRealm 至少告诉我们:同一把钥匙开的门里,终于可以有几间独立的房间了。

评论区

0 条评论

登录后可评论。

查看完整榜单
查看完整榜单
查看完整榜单