Javascript 如何使用 RequireJS/AMD 处理循环依赖?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4881059/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to handle circular dependencies with RequireJS/AMD?
提问by avernet
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:
在我的系统中,我在浏览器中加载了许多“类”,每个类在开发过程中都是一个单独的文件,并连接在一起用于生产。当它们被加载时,它们在全局对象上初始化一个属性,这里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");
Instead of using my own global object, I am considering to make each class its own AMD module, based on James Burke's suggestion:
我没有使用我自己的全局对象,而是考虑根据James Burke 的建议,让每个类都成为自己的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");
});
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.
问题是之前,Employee 和 Company 之间没有声明时依赖:你可以按照你想要的任何顺序放置声明,但是现在,使用 RequireJS,这引入了一个依赖,它在这里(故意)循环,所以上面的代码失败了。当然,在 中addEmployee()
,添加第一行var Employee = require("Employee");
会使其工作,但我认为此解决方案不如不使用 RequireJS/AMD,因为它要求我(开发人员)了解这个新创建的循环依赖并对此做一些事情。
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?
有没有更好的方法用 RequireJS/AMD 解决这个问题,还是我将 RequireJS/AMD 用于它不是设计的东西?
采纳答案by jrburke
This is indeed a restriction in the AMD format. You could use exports, and that problem goes away. I find exports to be ugly, but it is how regular CommonJS modules solve the problem:
这确实是 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;
});
Otherwise, the require("Employee") you mention in your message would work too.
否则,您在消息中提到的 require("Employee") 也会起作用。
In general with modules you need to be more aware of circular dependencies, AMD or not. Even in plain JavaScript, you have to be sure to use an object like the G object in your example.
一般来说,对于模块,您需要更多地了解循环依赖,无论是否 AMD。即使在纯 JavaScript 中,您也必须确保在示例中使用像 G 对象这样的对象。
回答by Pascalius
回答by redolent
If you don't need your dependencies to be loaded at the start (e.g., when you are extending a class), then this is what you can do: (taken from http://requirejs.org/docs/api.html#circular)
如果您不需要在开始时加载您的依赖项(例如,当您扩展类时),那么您可以这样做:(取自http://requirejs.org/docs/api.html#圆形)
In the file a.js
:
在文件中a.js
:
define( [ 'B' ], function( B ){
// Just an example
return B.extend({
// ...
})
});
And in the other file b.js
:
在另一个文件中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();
}
};
});
In the OP's example, this is how it would change:
在 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");
});
回答by yeahdixon
I looked at the docs on circular dependencies :http://requirejs.org/docs/api.html#circular
我查看了关于循环依赖的文档:http: //requirejs.org/docs/api.html#circular
If there is a circular dependency with a and b , it says in your module to add require as a dependency in your module like so :
如果 a 和 b 存在循环依赖关系,它会在您的模块中说在您的模块中添加 require 作为依赖项,如下所示:
define(["require", "a"],function(require, a) { ....
then when you need "a" just call "a" like so:
然后当您需要“a”时,只需像这样调用“a”:
return function(title) {
return require("a").doSomething();
}
This worked for me
这对我有用
回答by Shu
I would just avoid the circular dependency. Maybe something like:
我只想避免循环依赖。也许是这样的:
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);
I don't think it's a good idea to work around this issue and try to keep the circular dependency. Just feels like general bad practice. In this case it can work because you really require those modules for when the exported function is called. But imagine the case where modules are required and used in the actual definition functions itself. No workaround will make that work. That's probably why require.js fails fast on circular dependency detection in the dependencies of the definition function.
我认为解决这个问题并尝试保持循环依赖不是一个好主意。只是感觉就像一般的坏习惯。在这种情况下,它可以工作,因为在调用导出的函数时您确实需要这些模块。但是想象一下在实际定义函数本身中需要和使用模块的情况。没有任何解决方法可以使这项工作发挥作用。这可能就是为什么 require.js 在定义函数的依赖项中的循环依赖检测上很快失败的原因。
If you really have to add a work around, the cleaner one IMO is to require a dependency just in time (in your exported functions in this case), then the definition functions will run fine. But even cleaner IMO is just to avoid circular dependencies altogether, which feels really easy to do in your case.
如果您真的必须添加解决方法,更清晰的 IMO 是及时需要依赖项(在这种情况下,在您的导出函数中),那么定义函数将运行良好。但即使更干净的 IMO 也只是为了完全避免循环依赖,在您的情况下这感觉很容易做到。
回答by Gili
All the posted answers (except https://stackoverflow.com/a/25170248/14731) are wrong. Even the official documentation (as of November 2014) is wrong.
所有发布的答案(除了https://stackoverflow.com/a/25170248/14731)都是错误的。甚至官方文档(截至 2014 年 11 月)也是错误的。
The only solution that worked for me is to declare a "gatekeeper" file, and have it define any method that depends on the circular dependencies. See https://stackoverflow.com/a/26809254/14731for a concrete example.
对我有用的唯一解决方案是声明一个“看门人”文件,并让它定义任何依赖于循环依赖的方法。有关具体示例,请参阅https://stackoverflow.com/a/26809254/14731。
Here is why the above solutions will not work.
这就是上述解决方案不起作用的原因。
- You cannot:
- 你不能:
var a;
require(['A'], function( A ){
a = new A();
});
and then use a
later on, because there is no guarantee that this code block will get executed before the code block that uses a
. (This solution is misleading because it works 90% of the time)
然后a
稍后使用,因为无法保证此代码块会在使用a
. (此解决方案具有误导性,因为它在 90% 的时间内都有效)
- I see no reason to believe that
exports
is not vulnerable to the same race condition.
- 我认为没有理由相信它
exports
不会受到相同竞争条件的影响。
the solution to this is:
解决办法是:
//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);
}
});
now we can use these modules A and B in module C
现在我们可以在模块 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
});
回答by Bj?rn Tantau
In my case I solved the circular dependency by moving the code of the "simpler" object into the more complex one. For me that was a collection and a model class. I guess in your case I would add the Employee-specific parts of Company into the Employee class.
就我而言,我通过将“更简单”对象的代码移动到更复杂的对象中来解决循环依赖问题。对我来说,那是一个集合和一个模型类。我想在您的情况下,我会将 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");
});
A bit hacky, but it should work for simple cases. And if you refactor addEmployee
to take an Employee as parameter, the dependency should be even more obvious to outsiders.
有点hacky,但它应该适用于简单的情况。而如果重构addEmployee
为以 Employee 为参数,这种依赖对于外人来说应该更加明显。