Làm thế nào để xử lý các phụ thuộc vòng tròn với RequestJS / AMD?


79

Trong hệ thống của tôi, tôi có một số "lớp" được tải trong trình duyệt, mỗi lớp là một tệp riêng biệt trong quá trình phát triển và được nối với nhau để sản xuất. Khi chúng được tải, chúng khởi tạo một thuộc tính trên một đối tượng toàn cục, tại đây G, như trong ví dụ sau:

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

Thay vì sử dụng đối tượng toàn cục của riêng mình, tôi đang cân nhắc để tạo mô-đun AMD riêng của mỗi lớp , dựa trên đề xuất của 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");
});

Vấn đề là trước đây, không có sự phụ thuộc về thời gian khai báo giữa Nhân viên và Công ty: bạn có thể đặt khai báo theo bất kỳ thứ tự nào bạn muốn, nhưng bây giờ, bằng cách sử dụng RequestJS, điều này đưa ra một phụ thuộc, đây là thông tư (có chủ ý), mã trên không thành công. Tất nhiên, addEmployee()thêm một dòng đầu tiên var Employee = require("Employee");sẽ làm cho nó hoạt động , nhưng tôi thấy giải pháp này kém hơn so với việc không sử dụng RequestJS / AMD vì nó yêu cầu tôi, nhà phát triển, phải biết về sự phụ thuộc vòng tròn mới được tạo này và làm gì đó với nó.

Có cách nào tốt hơn để giải quyết vấn đề này với RequiJS / AMD, hay tôi đang sử dụng RequiJS / AMD cho thứ mà nó không được thiết kế?

Câu trả lời:


59

Đây thực sự là một hạn chế trong định dạng AMD. Bạn có thể sử dụng xuất và vấn đề đó sẽ biến mất. Tôi thấy xuất khẩu là xấu, nhưng đó là cách các mô-đun CommonJS thông thường giải quyết vấn đề:

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

Nếu không, yêu cầu ("Nhân viên") mà bạn đề cập trong tin nhắn của mình cũng sẽ hoạt động.

Nói chung với các mô-đun, bạn cần phải biết rõ hơn về các phụ thuộc vòng tròn, AMD hay không. Ngay cả trong JavaScript thuần túy, bạn phải đảm bảo sử dụng một đối tượng như đối tượng G trong ví dụ của mình.


3
Tôi nghĩ rằng bạn phải khai báo các xuất khẩu trong danh sách đối số của cả hai lệnh gọi lại, như function(exports, Company)function(exports, Employee). Dù sao thì, cảm ơn vì RequiJS, nó thật tuyệt.
Sébastien RoccaSerra

@jrburke Tôi nghĩ rằng điều này có thể được thực hiện một cách đúng đắn, đối với người trung gian hoặc cốt lõi hoặc thành phần từ trên xuống khác? Đây có phải là một ý tưởng khủng khiếp, để làm cho nó có thể truy cập được bằng cả hai phương pháp? stackoverflow.com/questions/11264827/…
SimplGy

1
Tôi không chắc mình hiểu cách giải quyết vấn đề này. Sự hiểu biết của tôi là tất cả các phụ thuộc phải được tải trước khi định nghĩa chạy. Đó không phải là trường hợp nếu "xuất khẩu" được thông qua như là phụ thuộc đầu tiên?
BT

1
bạn không bỏ lỡ xuất khẩu dưới dạng tham số trong chức năng?
shabunc

1
Để theo dõi điểm @ shabunc về các param xuất khẩu mất tích, xem câu hỏi này: stackoverflow.com/questions/28193382/...
Michael.Lumley

15

Tôi nghĩ rằng đây là một nhược điểm trong các dự án lớn hơn, nơi mà các phụ thuộc vòng tròn (đa cấp) nằm không bị phát hiện. Tuy nhiên, với madge, bạn có thể in một danh sách các phụ thuộc vòng tròn để tiếp cận chúng.

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

CACSVML-13295: sc-admin-ui-express amills001c $ madge --circular --format amd ./ Không tìm thấy phụ thuộc vòng tròn!
Alexander Mills

8

Nếu bạn không cần tải các phần phụ thuộc của mình khi bắt đầu (ví dụ: khi bạn mở rộng một lớp), thì đây là những gì bạn có thể làm: (lấy từ http://requirejs.org/docs/api.html# hình tròn )

Trong tệp a.js:

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

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

    });

Và trong tệp khác 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();
            }
        };

    });

Trong ví dụ của OP, đây là cách nó sẽ thay đổi:

    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
Như Gili đã nói trong bình luận của mình, giải pháp này là sai và sẽ không phải lúc nào cũng hiệu quả. Có một điều kiện chạy đua mà khối mã sẽ được thực thi trước.
Louis Ameline

6

Tôi đã xem các tài liệu về sự phụ thuộc vòng tròn: http://requirejs.org/docs/api.html#circular

Nếu có sự phụ thuộc vòng tròn với a và b, nó cho biết trong mô-đun của bạn để thêm yêu cầu làm phụ thuộc trong mô-đun của bạn như sau:

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

sau đó khi bạn cần "a" chỉ cần gọi "a" như vậy:

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

Điều này đã làm việc cho tôi


5

Tôi chỉ cần tránh sự phụ thuộc vòng tròn. Có thể một cái gì đó như:

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

Tôi không nghĩ rằng đó là một ý tưởng hay để giải quyết vấn đề này và cố gắng duy trì sự phụ thuộc của vòng tròn. Chỉ cảm thấy như thực hành xấu nói chung. Trong trường hợp này, nó có thể hoạt động vì bạn thực sự yêu cầu các mô-đun đó khi hàm đã xuất được gọi. Nhưng hãy tưởng tượng trường hợp các mô-đun được yêu cầu và sử dụng trong chính các chức năng định nghĩa thực tế. Không có cách giải quyết nào sẽ làm cho điều đó hoạt động. Đó có lẽ là lý do tại sao Requi.js không nhanh chóng trong việc phát hiện phụ thuộc vòng tròn trong các phụ thuộc của hàm định nghĩa.

Nếu bạn thực sự phải thêm công việc xung quanh, IMO rõ ràng hơn là yêu cầu phụ thuộc đúng lúc (trong trường hợp này là các hàm đã xuất của bạn), thì các hàm định nghĩa sẽ chạy tốt. Nhưng IMO rõ ràng hơn chỉ là tránh hoàn toàn các phụ thuộc vòng tròn, điều này thực sự dễ thực hiện trong trường hợp của bạn.


2
Bạn đang đề xuất đơn giản hóa mô hình miền và làm cho nó ít khả dụng hơn chỉ vì công cụ Requijs không hỗ trợ điều đó. Các công cụ được cho là giúp cuộc sống của nhà phát triển dễ dàng hơn. Mô hình miền khá đơn giản - nhân viên và công ty. Đối tượng nhân viên nên biết (những) công ty mình làm việc, các công ty nên có danh sách nhân viên. Mô hình miền là đúng, đó là công cụ không thành công ở đây
Dethariel

5

Tất cả các câu trả lời đã đăng (ngoại trừ https://stackoverflow.com/a/25170248/14731 ) đều sai. Ngay cả tài liệu chính thức (tính đến tháng 11 năm 2014) cũng sai.

Giải pháp duy nhất phù hợp với tôi là khai báo một tệp "gatekeeper" và để nó định nghĩa bất kỳ phương thức nào phụ thuộc vào các phụ thuộc vòng tròn. Xem https://stackoverflow.com/a/26809254/14731 để biết ví dụ cụ thể.


Đây là lý do tại sao các giải pháp trên sẽ không hoạt động.

  1. Bạn không thể:
var a;
require(['A'], function( A ){
     a = new A();
});

và sau đó sử dụng asau này, vì không có gì đảm bảo rằng khối mã này sẽ được thực thi trước khối mã sử dụng a. (Giải pháp này gây hiểu lầm vì nó hoạt động 90% thời gian)

  1. Tôi thấy không có lý do gì để tin rằng điều đó exportskhông dễ bị ảnh hưởng bởi cùng một chủng tộc.

giải pháp cho điều này là:

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

    });

bây giờ chúng ta có thể sử dụng các mô-đun A và B này trong mô-đun 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

    });

btw, nếu bạn vẫn gặp sự cố với điều này, câu trả lời của @ yeahdixon phải chính xác và tôi nghĩ bản thân tài liệu cũng đúng.
Alexander Mills

Tôi đồng ý rằng phương pháp luận của bạn hoạt động nhưng tôi nghĩ tài liệu này đúng và có thể tiến gần hơn một bước đến "đồng bộ".
Alexander Mills

bạn có thể vì tất cả các biến được đặt khi tải. Trừ khi người dùng của bạn là những người du hành thời gian và nhấp vào nút trước khi nó tồn tại. Nó sẽ phá vỡ quan hệ nhân quả và sau đó một điều kiện chủng tộc là có thể.
Eddie

0

Trong trường hợp của tôi, tôi đã giải quyết sự phụ thuộc vòng tròn bằng cách chuyển mã của đối tượng "đơn giản" thành đối tượng phức tạp hơn. Đối với tôi đó là một bộ sưu tập và một lớp người mẫu. Tôi đoán trong trường hợp của bạn, tôi sẽ thêm các bộ phận dành riêng cho Nhân viên của Công ty vào lớp Nhân viên.

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

Một chút hacky, nhưng nó sẽ hoạt động cho các trường hợp đơn giản. Và nếu bạn tái cấu trúc addEmployeeđể lấy một Nhân viên làm tham số, thì sự phụ thuộc sẽ càng rõ ràng hơn đối với người ngoài.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.