Как обрабатывать циклические зависимости с 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, основываясь на предложении Джеймса Берка:
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");
});
проблема в том, что раньше было нет зависимости от времени объявления между сотрудником и компанией: вы можете поместить объявление в любом порядке, но теперь, используя RequireJS, это вводит зависимость, которая здесь (намеренно) круговая, поэтому приведенный выше код не работает. Конечно, в addEmployee(), добавив первой строкой var Employee = require("Employee"); б заставить его работать, но я вижу, что это решение уступает не использованию RequireJS/AMD, поскольку оно требует от меня, разработчика, знать об этой недавно созданной циклической зависимости и что-то делать об этом.
есть ли лучший способ решить эту проблему с RequireJS/AMD, или я использую RequireJS / AMD для чего-то, для чего он не был разработан?
7 ответов:
Это действительно ограничение в формате 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; });в противном случае требование("сотрудник"), которое вы упоминаете в своем сообщении, тоже будет работать.
в целом с модулями вы должны быть более осведомлены о круговых зависимостях, AMD или нет. Даже в простом JavaScript, вы должны быть уверены, чтобы использовать объект, как объект G в вашем образец.
Я думаю, что это довольно недостаток в больших проектах, где (многоуровневые) циклические зависимости живут незамеченными. Однако, с Мэдж вы можете распечатать список циклических зависимостей приблизиться к ним.
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);Я не думаю, что это хорошая идея, чтобы обойти эту проблему и попытаться держать круговую зависимость. Просто похоже на общую плохую практику. В этом случае он может работать, потому что вам действительно нужны эти модули, когда экспортируемые функции. Но представьте себе случай, когда модули необходимы и используются в самих функциях определения. Никакое обходное решение не сделает эту работу. Наверное зачем требовать.js быстро терпит неудачу при обнаружении циклических зависимостей в зависимостях функции определения.
Если вам действительно нужно добавить работу, более чистый ИМО должен требовать зависимости как раз вовремя (в ваших экспортированных функциях в этом случае), то функции определения будут работать нормально. Но даже более чистый IMO-это просто избежать круговых зависимостей в целом, что очень легко сделать в вашем случае.
все опубликованные ответы (кроме https://stackoverflow.com/a/25170248/14731) ошибаются. Даже официальная документация (по состоянию на ноябрь 2014 года) - это неправильно.
единственное решение, которое сработало для меня, - это объявить файл "gatekeeper" и определить любой метод, который зависит от циклических зависимостей. Смотрите 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); } });теперь мы можем использовать эти модули A и B в модуле C
//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) { ....тогда, когда вам нужно "а" просто позвоните "а" вот так:
return function(title) { return require("a").doSomething(); }это сработало для меня
в моем случае я решил циклическую зависимость, переместив код "более простого" объекта в более сложный. Для меня это была коллекция и класс модели. Я думаю, в вашем случае я бы добавил отдельные части компании в класс сотрудников.
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чтобы взять сотрудника в качестве параметра, зависимость должна быть еще более очевидной для посторонних.
Comments