2011-02-02 5 views
75

В моей системе у меня есть несколько «классов», загруженных в обозревателе, каждый отдельный файл во время разработки и объединенный вместе для производства. Как они загружены, они инициализирует свойство на глобальный объект, здесь G, как в этом примере:Как обрабатывать круговые зависимости с RequireJS/AMD?

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

вопрос заключается в том, что до того, не было DECLARE-временная зависимость между Работником и компанией: вы можете поместить объявление в любом порядке, что вы хотели, но сейчас, используя RequireJS, это вводит зависимость, которая здесь (намеренно) круговой, поэтому приведенный выше код не работает. Конечно, в addEmployee() добавление первой строки var Employee = require("Employee"); было бы make it work, но я вижу это решение как уступающее отсутствию использования RequireJS/AMD, поскольку оно требует от меня, разработчика, знать об этой вновь созданной циклической зависимости и что-то делать с ней.

Есть ли лучший способ решить эту проблему с RequireJS/AMD, или я использую RequireJS/AMD для чего-то, для чего он не предназначен?

ответ

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

В противном случае требуется («Сотрудник») Вы упоминаете в своем сообщении будет работать тоже.

В целом с модулями вам нужно больше знать о круговых зависимостях, AMD или нет. Даже в простом JavaScript вы должны обязательно использовать объект, подобный объекту G в вашем примере.

+3

Я думал, вы должны были объявить экспорт в обоих обратных вызовов список аргументов, например 'function (export, Company)' и 'function (export, Employee)'. В любом случае, спасибо за RequireJS, это просто. –

+0

@jrburke Я думаю, что это можно сделать в одностороннем порядке, для медиатора или ядра или другого компонента сверху вниз? Это ужасная идея, чтобы сделать ее доступной с использованием обоих методов? http://stackoverflow.com/questions/11264827/circular-dependencies-in-modules-using-requirejs/17666014#17666014 – SimplGy

+1

Я не уверен, что понимаю, как это решает проблему. Я понимаю, что все зависимости должны быть загружены до запуска define. Не так ли, если «экспорт» передается в качестве первой зависимости? –

15

Я думаю, что это довольно недостаток в больших проектах, где (многоуровневые) круговые зависимости остаются необнаруженными. Однако с madge вы можете распечатать список круговых зависимостей, чтобы подойти к ним.

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

cool Я собираюсь проверить это –

+0

CACSVML -13295: sc-admin-ui-express amills001c $ madge --circular --format amd ./ Никаких круговых зависимостей не найдено! –

7

Если вам не нужны ваши зависимости должны быть загружены в начале (например, когда вы расширяете класс), то это то, что вы можете сделать: (взято из 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(); 
      } 
     }; 

    }); 

В примере Ор, это то, как она будет меняться:

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

Как сказал Гили в своем комментарии, это решение неверно и не всегда будет работать. Существует условие гонки, на котором сначала будет выполняться блок кода. –

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 должен требовать зависимости как раз вовремя (в этом случае экспортированные функции), тогда функции определения будут работать нормально. Но даже более чистая ИМО - это просто избежать круговых зависимостей вообще, что очень удобно в вашем случае.

+1

Вы предлагаете упростить модель домена и сделать ее менее пригодной для использования только потому, что средство requirejs не поддерживает это. Инструменты должны облегчить жизнь разработчика. Модель домена довольно проста - сотрудник и компания. Объект сотрудника должен знать, в какой компании (ах) он работает, компании должны иметь список сотрудников. Модель домена верна, это инструмент, который не работает здесь – Dethariel

5

Все опубликованные ответы (кроме https://stackoverflow.com/a/25170248/14731) являются неправильными. Даже официальная документация (по состоянию на ноябрь 2014 года) неверна.

Единственное решение, которое сработало для меня, - объявить файл «привратник» и определить его каким-либо способом, зависящим от круговых зависимостей. См. 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); 
     } 

    }); 

теперь мы можем использовать эти модули 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 

    }); 
+0

Кстати, если у вас все еще есть проблемы с этим, ответ @ yeahdixon должен быть правильным, и я думаю, что сама документация верна. –

+0

Я согласен с тем, что ваша методология работает, но я думаю, что документация правильная и может быть на один шаг ближе к «синхронному». –

5

Я смотрел на документы на круговых зависимостей: http://requirejs.org/docs/api.html#circular

Если существует круговая зависимость с a и b, в вашем модуле говорится, что добавить в качестве модуля в качестве модуля запрос l икэ так:

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

тогда, когда вам нужно «а» просто называют «» как так:

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

Это работало для меня

+0

это тоже работало –

Смежные вопросы