如何使用RequireJS / AMD处理循环依赖?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用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");
我没有使用自己的全局对象,而是考虑根据AMD module使每个类成为自己的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");
});
问题在于之前,Employee和Company之间没有声明时间依赖关系:你可以按照你想要的顺序放置声明,但现在,使用RequireJS,这引入了一个依赖,这里(故意)是循环的,所以上面的代码失败。当然,在addEmployee()
中,添加第一行var Employee = require("Employee");
会make it work,但我认为这个解决方案不如不使用RequireJS / AMD,因为它要求我(开发人员)了解这个新创建的循环依赖并做一些事情。
有没有更好的方法来解决RequireJS / AMD的这个问题,或者我是否使用RequireJS / AMD来设计它不适用的东西?
这确实是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;
});
否则,您在邮件中提到的要求(“员工”)也会起作用。
通常,对于模块,您需要更多地了解循环依赖关系,AND与否。即使在纯javascript中,您也必须确保在示例中使用类似G对象的对象。
我认为这在大型项目中是一个相当大的缺点,其中(多级)循环依赖性未被发现。但是,使用madge,您可以打印循环依赖项列表以接近它们。
madge --circular --format amd /path/src
如果您不需要在开始时加载依赖项(例如,当您扩展类时),那么这就是您可以执行的操作:(取自http://requirejs.org/docs/api.html#circular)
在文件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就是要及时要求依赖(在这种情况下在导出的函数中),那么定义函数将运行良好。但即使是更清洁的IMO也只是为了完全避免循环依赖,这在你的情况下感觉很容易。
所有发布的答案(https://stackoverflow.com/a/25170248/14731除外)都是错误的。即使是官方文件(截至2014年11月)也是错误的。
对我有用的唯一解决方案是声明一个“关守”文件,并让它定义任何依赖于循环依赖关系的方法。有关具体示例,请参阅https://stackoverflow.com/a/26809254/14731。
这就是上述解决方案不起作用的原因。
- 你不能:
var a;
require(['A'], function( A ){
a = new A();
});
然后使用a
,因为无法保证在使用a
的代码块之前执行此代码块。 (这种解决方案具有误导性,因为它可以在90%的时间内工作)
- 我认为没有理由相信
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
});
我查看了关于循环依赖的文档: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();
}
这对我有用
在我的例子中,我通过将“更简单”对象的代码移动到更复杂的对象中来解决循环依赖。对我来说,这是一个集合和一个模型类。我想在你的情况下,我会将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作为参数,对外人来说依赖性应该更加明显。
以上是关于如何使用RequireJS / AMD处理循环依赖?的主要内容,如果未能解决你的问题,请参考以下文章