Domanda

Nel mio sistema, ho un certo numero di "classi" caricate nel browser ciascuna a file separati durante lo sviluppo e concatenate insieme per la produzione. Man mano che vengono caricati, inizializzano una proprietà su un oggetto globale, qui G, come in questo esempio:

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

Invece di usare il mio oggetto globale, sto pensando di rendere ogni classe Modulo AMD, basato su Il suggerimento di James Burke:

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

Il problema è che prima, non vi era alcuna dipendenza di tempo di dichiarazione tra dipendente e azienda: potresti mettere la dichiarazione in qualsiasi ordine che volevi, ma ora, usando Requisitijs, questo introduce una dipendenza, che è qui (intenzionalmente) circolare, quindi il Il codice sopra fallisce. Certo, in addEmployee(), aggiungendo una prima riga var Employee = require("Employee"); voluto fallo funzionare, ma vedo questa soluzione come inferiore a non usare Requisiti/AMD come richiede me, lo sviluppatore, di essere consapevole di questa dipendenza circolare appena creata e fare qualcosa al riguardo.

Esiste un modo migliore per risolvere questo problema con Requisitijs/AMD, o sto usando Requisiti/AMD per qualcosa per cui non è stato progettato?

È stato utile?

Soluzione

Questa è davvero una restrizione nel formato AMD. Potresti usare le esportazioni e quel problema scompare. Trovo che le esportazioni siano brutte, ma è come i moduli Commonjs regolari risolvono il problema:

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

Altrimenti, il requisito ("dipendente") che menziona nel tuo messaggio funzionerebbe anche.

In generale con i moduli devi essere più consapevole delle dipendenze circolari, AMD o no. Anche in semplice JavaScript, devi essere sicuro di usare un oggetto come l'oggetto G nel tuo esempio.

Altri suggerimenti

Penso che questo sia piuttosto uno svantaggio in progetti più grandi in cui le dipendenze circolari (multilivello) si abitano inosservate. Tuttavia, con Madge Puoi stampare un elenco di dipendenze circolari per avvicinarle.

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

Se non hai bisogno che le tue dipendenze vengano caricate all'inizio (ad esempio, quando estendi una classe), allora questo è ciò che puoi fare: (preso da http://requirejs.org/docs/api.html#circular)

Nel file a.js:

    define( [ 'B' ], function( B ){

        // Just an example
        return B.extend({
            // ...
        })

    });

E nell'altro file 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();
            }
        };

    });

Nell'esempio dell'OP, è così che cambierebbe:

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

Eviterei solo la dipendenza circolare. Forse qualcosa di simile:

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

Non penso che sia una buona idea aggirare questo problema e cercare di mantenere la dipendenza circolare. Sembra solo una cattiva pratica generale. In questo caso può funzionare perché richiede davvero quei moduli per quando viene chiamata la funzione esportata. Ma immagina il caso in cui i moduli sono richiesti e utilizzati nelle funzioni di definizione effettiva stessa. Nessuna soluzione alternativa farà funzionare questo. Questo è probabilmente il motivo per cui Requisite.js fallisce rapidamente dal rilevamento della dipendenza circolare nelle dipendenze della funzione di definizione.

Se devi davvero aggiungere un lavoro in giro, il più pulito è richiedere una dipendenza appena in tempo (nelle funzioni esportate in questo caso), le funzioni di definizione funzionano bene. Ma anche l'IMO più pulito è solo per evitare del tutto le dipendenze circolari, il che sembra davvero facile da fare nel tuo caso.

Tutte le risposte pubblicate (tranne https://stackoverflow.com/a/25170248/14731) sono sbagliati. Anche la documentazione ufficiale (a novembre 2014) è sbagliata.

L'unica soluzione che ha funzionato per me è dichiarare un file "gatekeeper" e far definire qualsiasi metodo che dipenda dalle dipendenze circolari. Vedere https://stackoverflow.com/a/26809254/14731 Per un esempio concreto.


Ecco perché le soluzioni di cui sopra non funzionano.

  1. Non puoi:
var a;
require(['A'], function( A ){
     a = new A();
});

e poi usa a Più tardi, perché non vi è alcuna garanzia che questo blocco di codice verrà eseguito prima del blocco di codice che utilizza a. (Questa soluzione è fuorviante perché funziona al 90% delle volte)

  1. Non vedo alcun motivo per crederlo exports non è vulnerabile alle stesse condizioni di razza.

La soluzione a questo è:

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

    });

Ora possiamo usare questi moduli A e B nel modulo 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

    });

Ho guardato i documenti sulle dipendenze circolari:http://requirejs.org/docs/api.html#circular

Se c'è una dipendenza circolare con A e B, dice nel tuo modulo di aggiungere come una dipendenza nel modulo in questo modo:

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

Quindi quando hai bisogno di "A" basta chiamare "a" come così:

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

Questo ha funzionato per me

Nel mio caso ho risolto la dipendenza circolare spostando il codice dell'oggetto "più semplice" in quello più complesso. Per me quella era una collezione e una classe modello. Immagino che nel tuo caso aggiungerei le parti dell'azienda specifiche per i dipendenti nella classe dei dipendenti.

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

Un po 'hacky, ma dovrebbe funzionare per casi semplici. E se fai refactor addEmployee Per prendere un dipendente come parametro, la dipendenza dovrebbe essere ancora più ovvia per gli estranei.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top