80

In my system, I have a number of "classes" loaded in the browser each a separate files during development, and concatenated together for production. As they are loaded, they initialize a property on a global object, here G, as in this example:

var G = {};

G.Employee = function(name) {
    this.name = name;
    this.company = new G.Company(name + "'s own company");
};

G.Company = function(name) {
    this.name = name;
    this.employees = [];
};
G.Company.prototype.addEmployee = function(name) {
    var employee = new G.Employee(name);
    this.employees.push(employee);
    employee.company = this;
};

var john = new G.Employee("John");
var bigCorp = new G.Company("Big Corp");
bigCorp.addEmployee("Mary");

Instead of using my own global object, I am considering to make each class its own AMD module, based on James Burke's suggestion:

define("Employee", ["Company"], function(Company) {
    return function (name) {
        this.name = name;
        this.company = new Company(name + "'s own company");
    };
});
define("Company", ["Employee"], function(Employee) {
    function Company(name) {
        this.name = name;
        this.employees = [];
    };
    Company.prototype.addEmployee = function(name) {
        var employee = new Employee(name);
        this.employees.push(employee);
        employee.company = this;
    };
    return Company;
});
define("main", ["Employee", "Company"], function (Employee, Company) {
    var john = new Employee("John");
    var bigCorp = new Company("Big Corp");
    bigCorp.addEmployee("Mary");
});

The issue is that before, there was no declare-time dependency between Employee and Company: you could put the declaration in whatever order you wanted, but now, using RequireJS, this introduces a dependency, which is here (intentionally) circular, so the above code fails. Of course, in addEmployee(), adding a first line var Employee = require("Employee"); would make it work, but I see this solution as inferior to not using RequireJS/AMD as it requires me, the developer, to be aware of this newly created circular dependency and do something about it.

Is there a better way to solve this problem with RequireJS/AMD, or am I using RequireJS/AMD for something it was not designed for?

4

7 回答 7

59

这确实是 AMD 格式的一个限制。你可以使用导出,这个问题就消失了。我发现导出很难看,但这是常规 CommonJS 模块解决问题的方式:

define("Employee", ["exports", "Company"], function(exports, Company) {
    function Employee(name) {
        this.name = name;
        this.company = new Company.Company(name + "'s own company");
    };
    exports.Employee = Employee;
});
define("Company", ["exports", "Employee"], function(exports, Employee) {
    function Company(name) {
        this.name = name;
        this.employees = [];
    };
    Company.prototype.addEmployee = function(name) {
        var employee = new Employee.Employee(name);
        this.employees.push(employee);
        employee.company = this;
    };
    exports.Company = Company;
});

否则,您在消息中提到的 require("Employee") 也会起作用。

通常,对于模块,您需要更加了解循环依赖关系,AMD 与否。即使在纯 JavaScript 中,您也必须确保在示例中使用像 G 对象这样的对象。

于 2011-02-03T00:17:41.323 回答
15

我认为这在(多级)循环依赖项未被检测到的大型项目中是一个很大的缺点。但是,使用madge ,您可以打印一个循环依赖项列表来处理它们。

madge --circular --format amd /path/src
于 2013-10-19T18:10:19.960 回答
9

如果您不需要在开始时加载依赖项(例如,当您扩展一个类时),那么您可以这样做:(取自http://requirejs.org/docs/api.html#圆形

在文件中a.js

    define( [ 'B' ], function( B ){

        // Just an example
        return B.extend({
            // ...
        })

    });

在另一个文件中b.js

    define( [ ], function( ){ // Note that A is not listed

        var a;
        require(['A'], function( A ){
            a = new A();
        });

        return function(){
            functionThatDependsOnA: function(){
                // Note that 'a' is not used until here
                a.doStuff();
            }
        };

    });

在 OP 的示例中,这就是它的变化方式:

    define("Employee", [], function() {

        var Company;
        require(["Company"], function( C ){
            // Delayed loading
            Company = C;
        });

        return function (name) {
            this.name = name;
            this.company = new Company(name + "'s own company");
        };
    });

    define("Company", ["Employee"], function(Employee) {
        function Company(name) {
            this.name = name;
            this.employees = [];
        };
        Company.prototype.addEmployee = function(name) {
            var employee = new Employee(name);
            this.employees.push(employee);
            employee.company = this;
        };
        return Company;
    });

    define("main", ["Employee", "Company"], function (Employee, Company) {
        var john = new Employee("John");
        var bigCorp = new Company("Big Corp");
        bigCorp.addEmployee("Mary");
    });
于 2013-10-22T23:46:13.000 回答
7

我查看了有关循环依赖项的文档:http ://requirejs.org/docs/api.html#circular

如果存在与 a 和 b 的循环依赖关系,它会在您的模块中说明在您的模块中添加 require 作为依赖项,如下所示:

define(["require", "a"],function(require, a) { ....

然后当你需要“a”时,只需像这样调用“a”:

return function(title) {
        return require("a").doSomething();
    }

这对我有用

于 2015-06-25T21:17:24.200 回答
5

所有发布的答案(除了https://stackoverflow.com/a/25170248/14731)都是错误的。甚至官方文档(截至 2014 年 11 月)也是错误的。

对我有用的唯一解决方案是声明一个“看门人”文件,并让它定义任何依赖于循环依赖的方法。具体示例见https://stackoverflow.com/a/26809254/14731


这就是为什么上述解决方案不起作用的原因。

  1. 你不能:
var a;
require(['A'], function( A ){
     a = new A();
});

然后a稍后使用,因为不能保证这个代码块会在使用a. (此解决方案具有误导性,因为它在 90% 的时间内都有效)

  1. 我认为没有理由相信它exports不会受到相同种族条件的影响。

解决方案是:

//module A

    define(['B'], function(b){

       function A(b){ console.log(b)}

       return new A(b); //OK as is

    });


//module B

    define(['A'], function(a){

         function B(a){}

         return new B(a);  //wait...we can't do this! RequireJS will throw an error if we do this.

    });


//module B, new and improved
    define(function(){

         function B(a){}

       return function(a){   //return a function which won't immediately execute
              return new B(a);
        }

    });

现在我们可以在模块 C 中使用这些模块 A 和 B

//module C
    define(['A','B'], function(a,b){

        var c = b(a);  //executes synchronously (no race conditions) in other words, a is definitely defined before being passed to b

    });
于 2014-11-27T20:48:31.990 回答
5

我只是避免循环依赖。也许是这样的:

G.Company.prototype.addEmployee = function(employee) {
    this.employees.push(employee);
    employee.company = this;
};

var mary = new G.Employee("Mary");
var bigCorp = new G.Company("Big Corp");
bigCorp.addEmployee(mary);

我认为解决这个问题并尝试保持循环依赖并不是一个好主意。感觉就像一般的坏习惯。在这种情况下,它可以工作,因为您在调用导出函数时确实需要这些模块。但是想象一下实际定义函数本身需要和使用模块的情况。没有任何解决方法可以使这项工作。这可能是 require.js 在定义函数的依赖项中循环依赖检测失败的原因。

如果您真的必须添加一个解决方法,更简洁的 IMO 是及时要求一个依赖项(在这种情况下在您的导出函数中),然后定义函数将运行良好。但即使是更清洁的 IMO 也只是为了完全避免循环依赖,这在你的情况下感觉很容易做到。

于 2014-08-06T21:09:23.727 回答
0

在我的例子中,我通过将“更简单”对象的代码移动到更复杂的对象中来解决循环依赖问题。对我来说,那是一个集合和一个模型类。我想在您的情况下,我会将 Company 的 Employee 特定部分添加到 Employee 类中。

define("Employee", ["Company"], function(Company) {
    function Employee (name) {
        this.name = name;
        this.company = new Company(name + "'s own company");
    };
    Company.prototype.addEmployee = function(name) {
        var employee = new Employee(name);
        this.employees.push(employee);
        employee.company = this;
    };

    return Employee;
});
define("Company", [], function() {
    function Company(name) {
        this.name = name;
        this.employees = [];
    };
    return Company;
});
define("main", ["Employee", "Company"], function (Employee, Company) {
    var john = new Employee("John");
    var bigCorp = new Company("Big Corp");
    bigCorp.addEmployee("Mary");
});

有点hacky,但它应该适用于简单的情况。而且,如果您重构addEmployee以将 Employee 作为参数,则依赖关系对于外人来说应该更加明显。

于 2018-09-05T08:34:37.803 回答