使用RequireJS进行依赖注入

Dependency Injection with RequireJS

本文关键字:依赖 注入 RequireJS 使用      更新时间:2023-09-26

我可以拉伸多少RequireJS为我的应用程序提供依赖注入?举个例子,假设我有一个模型,我希望它是单例的。不是自我强制的getInstance()类型的单例,而是上下文强制的单例(每个"上下文"一个实例)。我想做一些事情,比如……

require(['mymodel'], function(mymodel) {
   ...
}

让mymodel成为mymodel类的一个实例。如果我要在多个模块中这样做,我希望我的模型是相同的,共享的实例。

我已经成功地使mymodel模块这样工作:

define(function() {
    var MyModel = function() {
        this.value = 10;
    }
    return new MyModel();
});

这种类型的使用是预期的和常见的,还是我滥用了RequireJS?是否有更合适的方式,我可以执行依赖注入与RequireJS?谢谢你的帮助。

这实际上不是依赖注入,而是服务位置:您的其他模块通过字符串"key "请求"类",并返回"服务定位器"(在本例中为RequireJS)已连接为它们提供的"类"的实例。

依赖注入包括返回MyModel构造函数,即return MyModel,然后在中央组合根中将MyModel的实例注入到其他实例中。我在这里整理了一个如何工作的示例:https://gist.github.com/1274607 (也在下面引用)

这样,组合根决定是否分发MyModel的单个实例(即使其单例作用域)或每个需要它的类的新实例(实例作用域),或介于两者之间。这个逻辑既不属于MyModel的定义,也不属于请求它的实例的类。

(旁注:虽然我没有使用它,但wire.js是一个成熟的JavaScript依赖注入容器,看起来很酷。)


你这样使用RequireJS并不一定是在滥用它,尽管你所做的看起来有点迂回,比如声明一个类而不是返回它的一个新实例。为什么不这样做呢?

define(function () {
    var value = 10;
    return {
        doStuff: function () {
            alert(value);
        }
    };
});

您可能错过的类比是,模块相当于大多数其他语言中的"名称空间",尽管您可以将函数和值附加到名称空间上。(所以更像Python,而不是Java或c#。)它们并不等同于类,尽管正如您所展示的,您可以使模块的导出等于给定类实例的导出。

所以你可以通过将函数和值直接附加到模块来创建单例,但这有点像通过使用静态类创建单例:它是高度不灵活的,通常不是最佳实践。然而,大多数人确实把他们的模块当作"静态类",因为正确地为依赖注入构建一个系统需要从一开始就进行大量的思考,这在JavaScript中并不是真正的规范。


这里是https://gist.github.com/1274607 inline:

// EntryPoint.js
define(function () {
    return function EntryPoint(model1, model2) {
        // stuff
    };
});
// Model1.js
define(function () {
    return function Model1() {
        // stuff
    };
});
// Model2.js
define(function () {
    return function Model2(helper) {
        // stuff
    };
});
// Helper.js
define(function () {
    return function Helper() {
        // stuff
    };
});
// composition root, probably your main module
define(function (require) {
    var EntryPoint = require("./EntryPoint");
    var Model1 = require("./Model1");
    var Model2 = require("./Model2");
    var Helper = require("./Helper");
    var entryPoint = new EntryPoint(new Model1(), new Model2(new Helper()));
    entryPoint.start();
});

如果您认真对待DI/IOC,您可能会对wire.js: https://github.com/cujojs/wire

感兴趣。

我们使用了服务重定位(像Domenic描述一样,但是使用curl.js而不是RequireJS)和DI(使用wire.js)的组合。在测试控制中使用模拟对象时,服务重定位非常方便。DI似乎是大多数其他用例的最佳选择。

在自我强制的getInstance()类型的单例中不是单例,而是上下文强制的单例(每个"上下文"一个实例)。

我建议它只用于静态对象。在require/define块中加载一个静态对象作为模块是完全可以的。然后创建一个只有静态属性和函数的类。然后你就有了相当于Math Object的对象,它有像PI、E、SQRT这样的常量和round()、random()、max()、min()这样的函数。非常适合创建可以随时注入的Utility类。

而不是:

define(function() {
    var MyModel = function() {
        this.value = 10;
    }
    return new MyModel();
});

创建一个实例,使用静态对象的模式(其中的值总是相同的对象永远不会被实例化):

define(function() {
    return {
       value: 10
    };
});

define(function() {
    var CONSTANT = 10;
    return {
       value: CONSTANT
    };
});

如果你想传递一个实例(使用一个返回new MyModel();的模块的结果),那么,在初始化函数中,传递一个捕获当前状态/上下文的变量,或者传递一个包含模块需要知道的状态/上下文信息的对象。