Skip to content

eval与sandbox

元信息

  • 目标:理解动态执行不可信代码的隔离问题——eval 无隔离、iframe 笨重、Worker 异步,认识 ShadowRealm 提案的独立全局环境与同步通信方案
  • 关键概念:eval、ShadowRealm、全局环境、evaluate、importValue、隔离
  • 关联阶段:cp3
  • 常见误区:以为 eval 的代码跑在与外界隔绝的盒子里(它就在当前作用域执行,毫无隔离);以为隔离只需换个作用域(须换整套 globalThis 与内建对象);以为 ShadowRealm 已可直接用于生产(尚在 TC39 Stage 3)

问题

我们经常需要处理来自不同来源的代码,无论是第三方的广告、分析脚本、用户自定义插件,还是动态加载的模块,将这些不受信任或半信任的代码集成到我们的应用中时,总会伴随着巨大的风险。

为了解决这些问题,开发者们曾尝试过各种方法,如 iframe、Web Workers,甚至是危险的 eval。但它们各有缺点:iframe 过于笨重,通信复杂;Web Workers 是异步的,不适合需要同步执行的场景;eval 则完全没有隔离性。

什么是 ShadowRealm?

ShadowRealm 是一个正在制定的 ECMAScript 标准,它允许开发者创建一个全新的、被隔离的 JavaScript 全局环境(Global Environment)

我们可以把它想象成一个轻量级的、纯粹为 JavaScript 准备的 iframe,但它没有 DOM、没有渲染开销,并且能够以一种可控的方式进行同步通信。

每个 ShadowRealm 实例都拥有自己独立的全局对象(globalThis)和一套完整的内建 JavaScript 对象(如 Object, Array, Promise 等)。在其中执行的代码无法访问主页面的 windowdocument,从而实现了强大的安全隔离。

核心概念与 API 用法

ShadowRealm 的 evaluate() 方法可以在 Realm 内部执行一段 JavaScript 代码字符串。它是同步的,直接返回代码的最终执行结果(只有原始值或函数可以跨越 Realm 边界,普通对象不行)。

javascript
const realm = new ShadowRealm();
const result = realm.evaluate('1+2');
console.log(result); // 3

注意evaluate 内部的代码无法访问外部作用域的任何变量。

截至目前,ShadowRealm API 处于 TC39 流程的 Stage 3,意味着其设计已经稳定,只待浏览器厂商广泛实现和最终批准。

导入函数:

evaluate() 很有用,但更强大的功能是与 Realm 内部的模块进行交互。importValue() 方法允许我们从 Realm 内异步导入一个模块的导出值(它才是返回 Promise 的那个):

javascript
// greet.js —— 一个普通的 ESM 模块
// export function greet(name){ return 'Hello from the Realm, ' + name + '!' }

const wrappedGreet = await realm.importValue('./greet.js', 'greet');
//                       第一个参数是模块地址 ↥    ↙ 第二个参数是导出名

// 调用这个“包装后”的函数
const message = await wrappedGreet('World');
console.log(message); // 输出: "Hello from the Realm, World!"

注意:importValue 的两个参数是模块地址 + 导出名,它导入的是模块的 export,而不是 evaluate 在 Realm 里定义的全局变量——跨 Realm 传值只认原始值和被包装的函数。