JavaScript如何为每个类实例分配独立的globals全局模块?
可行方案
按改动量从小到大排序:
方案1:Proxy+上下文栈(最优解,改动量极小)
优势
- 仅需要修改
globals.js和MyModule入口代码 - 其他所有文件的
import globals from './globals.js'语句完全不用修改 - 原有业务逻辑、
globals.js的导出属性结构完全不用调整
实现代码
首先修改globals.js:
// globals.js 仅修改此文件,原有属性结构保留 const globalStack = []; // 保留原来的默认配置 const defaultGlobals = { state1: true, }; // 导出Proxy自动匹配当前实例的globals export default new Proxy(defaultGlobals, { get(target, prop) { // 取栈顶的实例globals,没有则用默认值 const activeGlobals = globalStack.at(-1) || target; return activeGlobals[prop]; }, set(target, prop, value) { const activeGlobals = globalStack.at(-1) || target; activeGlobals[prop] = value; return true; } }); // 暴露给MyModule的上下文绑定方法 export function runWithIsolatedGlobals(fn) { // 拷贝默认值生成新的实例独立globals,有深拷贝需求可以替换成深拷贝逻辑 const instanceGlobals = { ...defaultGlobals }; globalStack.push(instanceGlobals); try { return fn(); } finally { // 执行完后出栈,避免上下文污染 globalStack.pop(); } }
然后修改index.js的MyModule构造函数:
import globals, { runWithIsolatedGlobals } from './globals.js'; import init from './init.js'; export default function MyModule (conf) { // 把原有构造函数的所有逻辑放到回调内即可 return runWithIsolatedGlobals(() => { console.log('in entry:', globals.state1); init(); // 原有其他构造逻辑、返回实例等 }); }
注意事项
如果构造函数/实例方法内有异步逻辑,单纯的栈结构会出现上下文错位,Node.js环境可以替换为AsyncLocalStorage实现异步上下文跟踪,浏览器环境可以接入Zone.js解决。
方案2:模块缓存隔离
优势
- 不需要修改
globals.js内部结构
缺点
- 需要把所有静态导入
globals的语句改为动态导入,改动量中等
实现逻辑
ES模块和CommonJS模块都会基于导入路径做缓存,给导入路径添加唯一查询参数即可视为不同模块,获得独立的globals实例:
// MyModule构造函数内动态导入 export default async function MyModule(conf) { const instanceId = Math.random().toString(36).slice(2); // 带唯一参数的导入会生成独立的模块缓存 const globals = await import(`./globals.js?instance=${instanceId}`).then(m => m.default); const init = await import(`./init.js?instance=${instanceId}`).then(m => m.default); console.log('in entry:', globals.state1); init(); }
方案3:依赖注入
优势
- 逻辑最直观,无黑魔法,长期可维护性最高
缺点
- 改动量最大,需要修改所有用到
globals的函数、类的参数
实现逻辑
把globals作为参数传递给所有依赖它的函数,每个实例创建独立的globals副本传入:
// init.js 修改为接收globals参数 export default function init (globals) { console.log('in init:', globals.state1); globals.entry1 = false; } // index.js import defaultGlobals from './globals.js'; import init from './init.js'; export default function MyModule (conf) { // 生成独立副本 const globals = { ...defaultGlobals }; console.log('in entry:', globals.state1); // 传入当前实例的globals init(globals); }
方案4:类成员封装
优势
- 符合面向对象设计,实例边界清晰
缺点
- 改动量大,需要把相关逻辑都封装到
MyModule类内部
实现逻辑
把globals作为实例属性,所有依赖方法改为类成员:
// index.js import defaultGlobals from './globals.js'; export default class MyModule { constructor(conf) { // 每个实例独立的globals this.globals = { ...defaultGlobals }; console.log('in entry:', this.globals.state1); this.init(); } init() { console.log('in init:', this.globals.state1); this.globals.entry1 = false; } }
内容的提问来源于stack exchange,提问作者Solo
相关产品推荐
相关产品推荐

