受限环境下在对象原型方法中使用JavaScript暴露模块模式的最优方案咨询
暴露模块模式在原型方法中的使用方案对比
你现在有一个用暴露模块模式写的全局JavaScript模块,没法用RequireJS这类模块化管理工具,也不能改动现有架构,想在某个对象的原型方法里调用它,纠结两种实现方式对吧?先看看你的基础模块代码:
// file1.js 中的暴露模块 var myModule = (function(){ function doSomething(){ // ... 模块内部逻辑 } return {doSomething: doSomething}; })();
接下来我们拆解你提到的两种方案,聊聊各自的优劣,以及有没有更合适的折中方式:
方案1:通过构造函数注入模块并保存为实例属性
示例代码:
function MyObject(myModule){ // ... 其他构造逻辑 this._myModule = myModule; } MyObject.prototype.doAnotherThing = function(){ // ... 其他逻辑 this._myModule.doSomething(); } // 使用时传入全局模块 var test = new MyObject(myModule);
方案1的优势:
- 解耦性更好:
MyObject不再直接绑定全局的myModule,如果以后模块名称变更、或者需要替换成其他实现,只需要在实例化时传入不同对象就行,不用修改MyObject本身的代码。 - 可测试性更强:单元测试时能轻松传入mock版本的
myModule,模拟不同返回结果,独立测试doAnotherThing的逻辑。 - 语义更清晰:从构造函数的参数就能直接看出
MyObject依赖这个模块,新人接手代码时更容易理解依赖关系。
方案1的小缺点:
- 每次实例化都要手动传入模块,但因为模块是全局的,其实就是传个全局变量,额外代码量极少。
- 实例会多一个
_myModule属性,这属于合理的依赖存储,几乎无负面影响。
方案2:在原型方法中直接引用全局模块
示例代码:
function MyObject(){ // ... 构造逻辑 } MyObject.prototype.doAnotherThing = function(){ // ... 其他逻辑 myModule.doSomething(); } // 直接实例化,不用传参 var test = new MyObject();
方案2的优势:
- 代码更简洁:不用在构造函数里处理参数,实例化时也少传一个参数,代码量更少。
- 写法更直观:如果模块确定是全局存在且不会变动的,直接引用全局变量看起来更直接。
方案2的缺点:
- 耦合度极高:
MyObject和全局的myModule强绑定,一旦模块名修改、或者模块被移除,所有用到它的原型方法都会报错,维护成本很高。 - 测试困难:单元测试时很难mock全局的
myModule,除非临时修改全局变量,这会影响其他测试用例的独立性。 - 可读性差:从
MyObject的定义里看不出它依赖这个模块,新人接手时可能会疑惑myModule的来源。
结论:优先选择方案1,或尝试折中方案
如果你的项目有后续维护、测试的需求,方案1是更优的选择——虽然多了几行代码,但解耦性和可测试性的提升是长期收益。如果你的代码是一次性的极简脚本,以后几乎不会改动,方案2也可以用,但还是更推荐方案1,毕竟代码的可维护性更重要。
另外还有一种折中方案,既保留方案1的优势,又兼顾方案2的简洁性:给构造函数的参数设置默认值(兼容旧环境可以用逻辑或替代),这样正常使用时不用传参,需要替换模块或测试时再传自定义对象:
// 支持ES6的环境写法 function MyObject(myModule = window.myModule){ this._myModule = myModule; } // 兼容旧环境的写法 function MyObject(myModule){ this._myModule = myModule || window.myModule; } MyObject.prototype.doAnotherThing = function(){ this._myModule.doSomething(); } // 正常使用不用传参 var test = new MyObject(); // 需要替换模块或测试时传自定义对象 var testWithMock = new MyObject(mockModule);
内容的提问来源于stack exchange,提问作者Remi C
相关产品推荐
相关产品推荐

