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

IT技术 javascript commonjs requirejs
2021-01-23 23:27:47

在我的系统中,我在浏览器中加载了许多“类”,每个类在开发过程中都是一个单独的文件,并连接在一起用于生产。当它们被加载时,它们在全局对象上初始化一个属性,这里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");

根据James Burke 的建议,我没有使用我自己的全局对象,而是考虑让每个类都有自己的AMD module

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 用于它不是设计的东西?

6个回答

这确实是 AMD 格式的限制。你可以使用导出,这个问题就会消失。我发现导出很难看,但这是常规 CommonJS module解决问题的方式:

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

否则,您在消息中提到的 require("Employee") 也会起作用。

一般来说,对于module,您需要更加了解循环依赖,无论是否 AMD。即使在纯 JavaScript 中,您也必须确保在示例中使用像 G 对象这样的对象。

@jrburke 我认为对于中介或核心或其他自上而下的组件,这可以单向完成吗?这是一个可怕的想法,使用这两种方法都可以访问它吗?stackoverflow.com/questions/11264827/...
2021-03-14 23:27:47
你不会想念导出作为函数中的参数吗?
2021-03-26 23:27:47
要跟进@shabunc 关于缺少导出参数的观点,请参阅此问题:stackoverflow.com/questions/28193382/...
2021-04-05 23:27:47
我认为您必须在两个回调的参数列表中声明导出,例如function(exports, Company)function(exports, Employee)无论如何,感谢 RequireJS,它很棒。
2021-04-06 23:27:47
我不确定我是否理解这是如何解决问题的。我的理解是必须在定义运行之前加载所有依赖项。如果“exports”作为第一个依赖项传递,情况不是这样吗?
2021-04-06 23:27:47

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

madge --circular --format amd /path/src
CACSVML-13295:sc-admin-ui-express amills001c$ madge --circular --format amd ./ 没有找到循环依赖!
2021-04-09 23:27:47

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

在文件中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");
    });
正如 Gili 在他的评论中所说,这个解决方案是错误的,并不总是有效。首先执行代码块存在竞争条件。
2021-03-30 23:27:47

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

如果 a 和 b 存在循环依赖关系,它会在您的module中说在您的module中添加 require 作为依赖项,如下所示:

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

然后当您需要“a”时,只需像这样调用“a”:

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

这对我有用

我只想避免循环依赖。也许是这样的:

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

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

如果您真的必须添加一个变通办法,更清晰的 IMO 是及时需要依赖项(在这种情况下在您的导出函数中),那么定义函数将运行良好。但即使更干净的 IMO 也只是为了完全避免循环依赖,在您的情况下这感觉很容易。

您建议简化域模型并降低其可用性,因为 requirejs 工具不支持它。工具应该使开发人员的生活更轻松。域模型非常简单——员工和公司。员工对象应该知道他为哪家公司工作,公司应该有员工名单。领域模型是对的,它是这里失败的工具
2021-03-25 23:27:47