Circular Dependencies in modules using requireJs

2019-02-12 19:17发布

问题:

Reading the requireJs documentation,
in order to fix the Circular Dependencies, is suggested to use exports to create an empty object for the module that is available immediately for reference by other modules.

I try this code but it seems to do not work. What is wrong?

P.S.:
read the comments for seeing the output,
especially the B module inside setTimeout call.


// A module
define([
    'b'
], function (b) {
    console.log('B:', b); // B, Object
    var A = {
        boo: 1
    };

    return A;
});

// B module
define([
    'a',
    'exports'
], function (a, exports) {
    console.log('A:', a); // A, undefined (as I was expecting)
    exports.A = function () {
        return a;
    }

    var B = {
        bar: 1
    };

    setTimeout(function () {
        console.log('exports.A', exports.A()); // exports.A undefined 
                                           // I would like to access the A object
                                           // which is defined in A module
    }, 500);

    return B;
});

// main.js

(function () {

    define([
        'a'
    ], function () {
    });
}());

回答1:

You should be able to use the synchronous version of require() in your B module to access the "A" module:

// B module
define([
    'a',
    'exports'
], function (a, exports) {
    console.log('A:', a); // A, undefined (as I was expecting)
    exports.A = function () {
        return require('a');
    }
    ...
});


回答2:

I often have circular issues using AMD modules to build an application core that both stands up many modules and contains config or other useful objects for those modules to use.

I did some experimenting today and this seems to work pretty well.

define(['exports', 'underscore', './config', './mediator'],
  function (exports, _, Backbone, config, Mediator){

    Core = /* ... */

    // Publicize a core 'singleton' so that it's dependencies can access it, and so can modules that define it as a dependency themselves.
    core = new Core()
    exports.core = core //publicize it in a way that supports circularity
    return core // And also publicize it normally
  }
)

The objects are both '===' equal to each other, so this seems very promising.

EDIT:

The above method doesn't work when optimized. Here's another method that may (untested): https://github.com/requirejs/example-multipage/blob/master/www/js/app/main1.js#L2

define(function (require) {
  var $ = require('jquery'),
      lib = require('./lib'),
      Core;

   Core = /* ... */

   return new Core()
});


回答3:

One option would be not to return the module itself, but a function that instantiates the module (in this example it would a constructor as defined in typescript, at the bottom is the generated js code -note that interfaces do not generate .js code)

  • File IA.ts

    /// <reference path="IB.ts" />
    interface IA{
        funcA();
        _classB : IB;
    }
    
  • File IB.ts

    /// <reference path="IA.ts" />
    interface IB{
        funcB();
        _classA : IA;
    }
    
  • File ClassA.ts

    /// <reference path="IA.ts" />
    /// <reference path="IB.ts" />
    
    export class ClassA implements IA
    {
        _classB : IB = null;
    
        constructor(classB : IB)
        {
            this._classB = classB;
            if (classB){
                this._classB._classA = this;
            }
            return this;
        }
    
        funcA(){
            console.log('I am ClassA');
        }
    }
    
  • File ClassB.ts

    /// <reference path="IA.ts" />
    /// <reference path="IB.ts" />
    export class ClassB implements IB
    {
        _classA : IA = null;
        constructor(classA : IA)
        {
            this._classA = classA;
            if (classA){
                this._classA._classB = this;
            }
            return this;
        }
        funcB(){
            console.log('I am ClassB');
        }
    }
    
  • File MainTest.ts

    /// <reference path="../../def/require.d.ts" />
    /// <reference path="IA.ts" />
    /// <reference path="IB.ts" />
    define(['ClassA', 'ClassB'],
        function (classA, classB)
        {
            var aa : IA = new classA.ClassA();
            var bb : IB = new classB.ClassB(aa);
    
            bb.funcB();
            aa._classB.funcB();
            bb._classA.funcA();
            aa.funcA();
        });
    

And the generated js code:

  • File ClassA.js

    define(["require", "exports"], function(require, exports) {
        var ClassA = (function () {
            function ClassA(classB) {
                this._classB = null;
                this._classB = classB;
                if (classB) {
                    this._classB._classA = this;
                }
                return this;
            }
            ClassA.prototype.funcA = function () {
                console.log('I am ClassA');
            };
            return ClassA;
        })();
        exports.ClassA = ClassA;
    });
    
  • File ClassB.js

    define(["require", "exports"], function(require, exports) {
        var ClassB = (function () {
            function ClassB(classA) {
                this._classA = null;
                this._classA = classA;
                if (classA) {
                    this._classA._classB = this;
                }
                return this;
            }
            ClassB.prototype.funcB = function () {
                console.log('I am ClassB');
            };
            return ClassB;
        })();
        exports.ClassB = ClassB;
    });
    
  • File MainTest.js

    define(['ClassA', 'ClassB'], function (classA, classB) {
    
        var aa = new classA.ClassA();
        var bb = new classB.ClassB(aa);
    
        bb.funcB();
        aa._classB.funcB();
        bb._classA.funcA();
        aa.funcA();
    
    });
    

finally, the output will be:

I am ClassB

I am ClassB

I am ClassA

I am ClassA