esm模块在不同realm(如主页面与iframe)中不共享模块实例、全局对象和原型链,因每个realm拥有独立全局环境、内置对象及原型,模块缓存按realm分区,导致相同路径模块产生完全独立实例。

ESM 模块在不同 Realm(如主页面与 iframe)之间天然不共享模块实例、全局对象和原型链,这不是“隔离策略”的结果,而是 JavaScript 语言规范定义的 Realm 本质——每个 Realm 拥有独立的全局环境、内置对象(Array、Promise、Date 等)和原型对象。ESM 的模块缓存(Module Map)也按 Realm 分区,因此即使导入相同路径的模块,在 iframe 中加载的 ESM 和主页面中加载的 ESM 是两个完全独立的模块实例。
模块缓存不跨 Realm
ESM 使用 per-Realm module map:模块解析与实例化绑定到当前执行上下文的 Realm。例如:
- 主页面执行
import { foo } from './utils.js'→ 创建并缓存一个模块记录(Module Record),其[[Realm]]指向主页面 Realm; - iframe 内执行同样语句 → 解析同一路径,但触发全新实例化流程,生成另一个模块记录,
[[Realm]]指向 iframe 的 Realm; - 二者导出的函数虽代码相同,但构造器、
instanceof判定、闭包捕获的全局对象均不同。
原型链物理分离
iframe 的 Realm 拥有自己的一套原生构造函数和原型对象。这意味着:
-
iframe.contentWindow.Array !== window.Array,哪怕都叫Array; -
new iframe.contentWindow.Array(1,2) instanceof window.Array返回false; -
iframe.contentWindow.Promise.prototype.then !== window.Promise.prototype.then; - 若在 iframe 中通过
Object.prototype.toString = ...修改原型,只影响该 iframe 内部,主页面的toString完全不受干扰。
ESM + Realm 隔离的实际影响
这种深度隔离带来明确行为边界,但也引入常见误区:
-
不能直接共享类实例:iframe 中用 ESM 导出的
class Model,主页面无法用instanceof Model判定其子类实例(因构造器来自不同 Realm); -
JSON 是安全桥梁:跨 Realm 通信推荐序列化/反序列化(如
postMessage传 plain object),避免传递函数或自定义类实例; -
polyfill 需按 Realm 单独注入:若 iframe 需要
Promise.allSettled,必须在 iframe 内单独加载 polyfill,主页面的 polyfill 对其无效; - tree-shaking 与打包无关:Rollup/Vite 的摇树仅作用于单个构建产物;不同 Realm 加载的 ESM 模块互不可见,不存在“跨 Realm 共享 chunk”一说。
对比 IIFE 与沙箱 Proxy 的局限性
IIFE 或基于 Proxy + with 的 JS 沙箱,只能模拟作用域隔离,无法分裂 Realm:
- 它们仍运行在宿主 Realm 中,共用
window、Array、Object.prototype; - 恶意代码仍可篡改全局原型(除非额外冻结,如
Object.freeze(Array.prototype)); - 而 iframe 提供的是浏览器级 Realm 隔离,是目前前端唯一能实现“真正原型级隔离”的原生机制。










