如何使用RequireJS/AMD处理循环依赖?

ave*_*net 78 javascript commonjs requirejs

在我的系统中,我在浏览器中加载了许多"类",每个文件在开发过程中都是单独的文件,并连接在一起进行生产.在加载它们时,它们会在全局对象上初始化一个属性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");
Run Code Online (Sandbox Code Playgroud)

我没有使用自己的全局对象,而是根据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");
});
Run Code Online (Sandbox Code Playgroud)

问题在于之前,Employee和Company之间没有声明时间依赖关系:你可以按照你想要的顺序放置声明,但现在,使用RequireJS,这引入了一个依赖,这里(故意)是循环的,所以上面的代码失败.当然,在addEmployee()补充一线var Employee = require("Employee");使它的工作,但我看到这个解决方案,不如不使用RequireJS/AMD,因为它需要我,开发商,要知道这个新创建循环依赖,并做一些事情.

有没有更好的方法来解决RequireJS/AMD的这个问题,或者我是否使用RequireJS/AMD来设计它不适用的东西?

jrb*_*rke 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;
});
Run Code Online (Sandbox Code Playgroud)

否则,您在邮件中提到的要求("员工")也会起作用.

通常,对于模块,您需要更多地了解循环依赖关系,AMD与否.即使在纯JavaScript中,您也必须确保在示例中使用类似G对象的对象.

  • 我认为你必须在两个回调参数列表中声明导出,比如`function(exports,Company)`和`function(exports,Employee)`.无论如何,感谢RequireJS,它太棒了. (3认同)

Pas*_*ius 15

我认为这在大型项目中是一个相当大的缺点,其中(多级)循环依赖性未被发现.但是,使用madge,您可以打印循环依赖关系列表以接近它们.

madge --circular --format amd /path/src
Run Code Online (Sandbox Code Playgroud)


red*_*ent 8

如果您不需要在开始时加载依赖项(例如,当您扩展类时),那么您可以这样做:(取自http://requirejs.org/docs/api.html#通告)

在文件中a.js:

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

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

    });
Run Code Online (Sandbox Code Playgroud)

在另一个文件中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();
            }
        };

    });
Run Code Online (Sandbox Code Playgroud)

在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");
    });
Run Code Online (Sandbox Code Playgroud)

  • 正如吉利在评论中所说,这种解决方案是错误的,并不总是有效.存在竞争条件,首先执行代码块. (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);
Run Code Online (Sandbox Code Playgroud)

我不认为解决这个问题并尝试保持循环依赖是个好主意.只是感觉像一般的坏习惯.在这种情况下,它可以工作,因为在调用导出的函数时,您确实需要这些模块.但想象一下在实际定义函数本身中需要和使用模块的情况.没有解决方法可以使这项工作.这可能就是为什么require.js在定义函数的依赖关系中的循环依赖检测上快速失败的原因.

如果你真的需要添加一个解决方法,那么更干净的一个IMO就是要及时要求依赖(在这种情况下在导出的函数中),那么定义函数将运行良好.但即使是更清洁的IMO也只是为了完全避免循环依赖,这在你的情况下感觉很容易.

  • 您建议简化域模型并使其不太可用,因为requirejs工具不支持它.工具应该让开发人员的生活更轻松.域模型非常简单 - 员工和公司.员工对象应该知道他为哪个公司工作,公司应该有一份员工清单.域模型是正确的,它是在这里失败的工具 (2认同)

Gil*_*ili 5

所有发布的答案(/sf/answers/1761917391/除外)都是错误的.即使是官方文件(截至2014年11月)也是错误的.

对我有用的唯一解决方案是声明一个"关守"文件,并让它定义任何依赖于循环依赖关系的方法.有关具体示例,请参阅/sf/answers/1876647811/.


这就是上述解决方案不起作用的原因.

  1. 你不能:
var a;
require(['A'], function( A ){
     a = new A();
});
Run Code Online (Sandbox Code Playgroud)

然后再使用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);
        }

    });
Run Code Online (Sandbox Code Playgroud)

现在我们可以在模块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

    });
Run Code Online (Sandbox Code Playgroud)


yea*_*xon 5

我查看了关于循环依赖的文档:http://requirejs.org/docs/api.html#circular

如果a和b存在循环依赖关系,它会在模块中将require作为依赖项添加到模块中,如下所示:

define(["require", "a"],function(require, a) { ....
Run Code Online (Sandbox Code Playgroud)

然后,当你需要"a"时,只需调用"a",如下所示:

return function(title) {
        return require("a").doSomething();
    }
Run Code Online (Sandbox Code Playgroud)

这对我有用