如何处理RequireJS / AMD的循环依赖?

在我的系统中,我在浏览器中加载了一些“类”,每个“类”在开发过程中分别存放一个文件,并连接在一起用于生产。 当它们被加载时,它们在全局对象(这里是G上初始化一个属性,如下例所示:

 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"); 

我不是用自己的全球对象,而是根据詹姆斯·伯克的build议 ,考虑让每个class级都有自己的AMD模块 :

 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"); }); 

问题是之前,Employee和Company之间没有声明时间依赖性:你可以按照你想要的顺序来放置声明,但是现在使用RequireJS,这引入了一个依赖关系,在这里(有意)是循环的,所以上面的代码失败。 当然,在addEmployee()中添加第一行var Employee = require("Employee"); 会使它工作 ,但是我认为这个解决scheme不如不使用RequireJS / AMD,因为它要求我,开发人员,要知道这个新创build的循环依赖关系,并做一些事情。

有没有更好的方法来解决RequireJS / AMD的这个问题,还是我使用RequireJS / AMD的东西是不是devise的?

这确实是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(“员工”)也会起作用。

一般来说,模块需要更多地了解循环依赖,AMD或不。 即使在普通的JavaScript中,您也必须确保在示例中使用像G对象一样的对象。

我认为这是大型项目(多层次)循环依赖关系未被发现的一个缺点。 但是,通过madge,您可以打印一个循环依赖列表来接近它们。

 madge --circular --format amd /path/src 

如果你不需要在开始时加载你的依赖关系(例如,当你正在扩展一个类),那么这是你可以做的:(取自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"); }); 

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

 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是要求及时(在这种情况下在你的导出函数)的依赖项,那么定义函数将运行良好。 但更简洁的海事组织只是为了避免循环依赖,这对你来说真的很容易。

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

我唯一的解决scheme是声明一个“关守”文件,并定义任何依赖于循环依赖的方法。 有关具体示例,请参阅https://stackoverflow.com/a/26809254/14731


这就是为什么上述解决scheme将无法正常工作。

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

然后再使用,因为不能保证这个代码块会在使用a的代码块之前被执行。 (这个解决scheme是误导的,因为它在90%的时间内工作)

  1. 我看不出有任何理由相信exports不容易受到相同的竞争条件的影响。

对此的解决scheme是:

 //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 }); 

我看了循环依赖的文档: 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(); } 

这对我有效