如何在Node.js中处理循环依赖

时间:2021-07-29 18:33:35

I've been working with nodejs lately and still getting to grips with the module system so apologies if this is an obvious question. I want code roughly like the following below:

我最近一直在和nodejs一起工作,并且还在研究模块系统,如果这是一个明显的问题,我很抱歉。我希望代码大致如下所示:

a.js (the main file run with node)

一个。js(带节点运行的主文件)

var ClassB = require("./b");

var ClassA = function() {
    this.thing = new ClassB();
    this.property = 5;
}

var a = new ClassA();

module.exports = a;

b.js

研究

var a = require("./a");

var ClassB = function() {
}

ClassB.prototype.doSomethingLater() {
    util.log(a.property);
}

module.exports = ClassB;

My problem seems to be that I can't access the instance of ClassA from within an instance of ClassB.

我的问题似乎是,我不能从ClassB的实例中访问ClassA实例。

Is there a correct / better way to structure modules to achieve what I want? Is there a better way to share variables between modules?

是否有一种正确/更好的方式来构建模块以实现我想要的?是否有更好的方法在模块之间共享变量?

12 个解决方案

#1


50  

While node.js does allow circular require dependencies, as you've found it can be pretty messy and you're probably better off restructuring your code to not need it. Maybe create a third class that uses the other two to accomplish what you need.

当节点。js确实允许循环需要依赖项,因为您已经发现它可能非常混乱,您最好对代码进行重组,使之不需要它。也许可以创建一个第三类,使用另外两个类来完成您需要的。

#2


125  

Try to set properties on module.exports, instead of replacing it completely. E.g., module.exports.instance = new ClassA() in a.js, module.exports.ClassB = ClassB in b.js. When you make circular module dependencies, the requiring module will get a reference to an incomplete module.exports from the required module, which you can add other properties latter on, but when you set the entire module.exports, you actually create a new object which the requiring module has no way to access.

尝试在模块上设置属性。出口,而不是完全替代。例如,module.exports。实例=新的ClassA()。js,module.exports。b。当您进行循环模块依赖时,需要模块将获得一个不完整模块的引用。从所需的模块导出,稍后您可以添加其他属性,但是在设置整个模块时。导出,实际上是创建一个新对象,这个对象需要模块无法访问。

#3


37  

[EDIT] it's not 2015 and most libraries (i.e. express) have made updates with better patterns so circular dependencies are no longer necessary. I recommend simply not using them.

[编辑]现在不是2015年,大多数库(比如express)已经更新了更好的模式,因此不再需要循环依赖。我建议不要使用它们。


I know I'm digging up an old answer here... The issue here is that module.exports is defined after you require ClassB. (which JohnnyHK's link shows) Circular dependencies work great in Node, they're just defined synchronously. When used properly, they actually solve a lot of common node issues (like accessing express.js app from other files)

Just make sure your necessary exports are defined before you require a file with a circular dependency.

只需确保在需要具有循环依赖项的文件之前定义了必要的导出。

This will break:

这将打破:

var ClassA = function(){};
var ClassB = require('classB'); //will require ClassA, which has no exports yet

module.exports = ClassA;

This will work:

这将工作:

var ClassA = module.exports = function(){};
var ClassB = require('classB');

I use this pattern all the time for accessing the express.js app in other files:

我一直使用这种模式来访问express。其他文件中的js app:

var express = require('express');
var app = module.exports = express();
// load in other dependencies, which can now require this file and use app

#4


25  

Sometimes it is really artificial to introduce a third class (as JohnnyHK advises), so in addition to Ianzz: If you do want to replace the module.exports, for example if you're creating a class (like the b.js file in the above example), this is possible as well, just make sure that in the file that is starting the circular require, the 'module.exports = ...' statement happens before the require statement.

有时引入第三类(正如JohnnyHK建议的那样)确实是人为的,所以除了Ianzz之外:如果您确实想要替换模块的话。导出,例如,如果您正在创建一个类(如b)。上面例子中的js文件),这也是可能的,只要确保在开始循环要求的文件中有'module。出口=…语句发生在require语句之前。

a.js (the main file run with node)

一个。js(带节点运行的主文件)

var ClassB = require("./b");

var ClassA = function() {
    this.thing = new ClassB();
    this.property = 5;
}

var a = new ClassA();

module.exports = a;

b.js

研究

var ClassB = function() {
}

ClassB.prototype.doSomethingLater() {
    util.log(a.property);
}

module.exports = ClassB;

var a = require("./a"); // <------ this is the only necessary change

#5


10  

The solution is to 'forward declare' your exports object before requiring any other controller. So if you structure all your modules like this and you won't run into any issues like that:

解决方案是在需要任何其他控制器之前'forward declare'您的export对象。所以,如果你像这样组织所有的模块,你就不会遇到这样的问题:

// Module exports forward declaration:
module.exports = {

};

// Controllers:
var other_module = require('./other_module');

// Functions:
var foo = function () {

};

// Module exports injects:
module.exports.foo = foo;

#6


7  

A solution which require minimal change is extending module.exports instead of overriding it.

一个需要最小改变的解决方案是扩展模块。出口,而不是压倒它。

a.js - app entry point and module which use method do from b.js*

一个。js - app入口点和使用方法的模块从b.js*。

_ = require('underscore'); //underscore provides extend() for shallow extend
b = require('./b'); //module `a` uses module `b`
_.extend(module.exports, {
    do: function () {
        console.log('doing a');
    }
});
b.do();//call `b.do()` which in turn will circularly call `a.do()`

b.js - module which use method do from a.js

b。js -用方法做的模块

_ = require('underscore');
a = require('./a');

_.extend(module.exports, {
    do: function(){
        console.log('doing b');
        a.do();//Call `b.do()` from `a.do()` when `a` just initalized 
    }
})

It will work and produce:

它将发挥作用并产生:

doing b
doing a

While this code will not work:

虽然该代码不能工作:

a.js

a.js

b = require('./b');
module.exports = {
    do: function () {
        console.log('doing a');
    }
};
b.do();

b.js

研究

a = require('./a');
module.exports = {
    do: function () {
        console.log('doing b');
    }
};
a.do();

Output:

输出:

node a.js
b.js:7
a.do();
    ^    
TypeError: a.do is not a function

#7


2  

What about lazy requiring only when you need to? So your b.js looks as follows

如果只在需要的时候才要求懒惰呢?所以你的b。js看起来如下所示

var ClassB = function() {
}
ClassB.prototype.doSomethingLater() {
    var a = require("./a");    //a.js has finished by now
    util.log(a.property);
}
module.exports = ClassB;

Of course it is good practice to put all require statements on top of the file. But there are occasions, where I forgive myself for picking something out of an otherwise unrelated module. Call it a hack, but sometimes this is better than introducing a further dependency, or adding an extra module or adding new structures (EventEmitter, etc)

当然,将所有require语句放在文件顶部是很好的实践。但有些时候,我原谅自己从一个不相关的模块中挑选出一些东西。称之为黑客,但有时这比引入进一步的依赖、添加额外的模块或添加新的结构(event发射器等)要好。

#8


1  

Similar to lanzz and setect's answers, I have been using the following pattern:

类似于lanzz和setect的答案,我一直使用以下模式:

module.exports = Object.assign(module.exports, {
    firstMember: ___,
    secondMember: ___,
});

The Object.assign() copies the members into the exports object that has already been given to other modules.

assign()将成员复制到已提供给其他模块的export对象中。

The = assignment is logically redundant, since it is just setting module.exports to itself, but I am using it because it helps my IDE (WebStorm) to recognise that firstMember is a property of this module, so "Go To -> Declaration" (Cmd-B) and other tooling will work from other files.

赋值在逻辑上是多余的,因为它只是设置模块。对其本身的导出,但我使用它是因为它帮助我的IDE (WebStorm)认识到firstMember是这个模块的属性,所以“Go to ->声明”(Cmd-B)和其他工具将从其他文件中工作。

This pattern is not very pretty, so I only use it when a cyclic dependency issue needs to be resolved.

这个模式不是很漂亮,所以我只在需要解决循环依赖问题时才使用它。

#9


1  

An other method I've seen people do is exporting at the first line and saving it as a local variable like this:

我看到的另一种方法是在第一行导出并将其作为本地变量保存如下:

let self = module.exports = {};

const a = require('./a');

// Exporting the necessary functions
self.func = function() { ... }

I tend to use this method, do you know about any downsides of it?

我喜欢用这种方法,你知道它有什么缺点吗?

#10


0  

Actually I ended up requiring my dependency with

事实上,我最终需要依赖

 var a = null;
 process.nextTick(()=>a=require("./a")); //Circular reference!

not pretty, but it works. It is more understandable and honest than changing b.js (for example only augmenting modules.export), which otherwise is perfect as is.

虽然不漂亮,但很管用。这比改变b更容易理解和诚实。js(例如,只增加模块。export),否则就很完美。

#11


0  

You can solve this easily: just export your data before you require anything else in modules where you use module.exports:

您可以很容易地解决这个问题:在使用模块时需要任何其他模块之前,只需导出数据。

classA.js

classA.js

class ClassA {

    constructor(){
        ClassB.someMethod();
        ClassB.anotherMethod();
    };

    static someMethod () {
        console.log( 'Class A Doing someMethod' );
    };

    static anotherMethod () {
        console.log( 'Class A Doing anotherMethod' );
    };

};

module.exports = ClassA;
var ClassB = require( "./classB.js" );

let classX = new ClassA();

classB.js

classB.js

class ClassB {

    constructor(){
        ClassA.someMethod();
        ClassA.anotherMethod();
    };

    static someMethod () {
        console.log( 'Class B Doing someMethod' );
    };

    static anotherMethod () {
        console.log( 'Class A Doing anotherMethod' );
    };

};

module.exports = ClassB;
var ClassA = require( "./classA.js" );

let classX = new ClassB();

#12


-4  

for your problem, you can use function declarations.

对于您的问题,可以使用函数声明。

class-b.js:

class-b.js:

var ClassA = require('./class-a')

module.exports = ClassB

function ClassB() {

}

class-a.js:

class-a.js:

var classB = require('./class-b')

module.exports = ClassA

function ClassA() {

}

#1


50  

While node.js does allow circular require dependencies, as you've found it can be pretty messy and you're probably better off restructuring your code to not need it. Maybe create a third class that uses the other two to accomplish what you need.

当节点。js确实允许循环需要依赖项,因为您已经发现它可能非常混乱,您最好对代码进行重组,使之不需要它。也许可以创建一个第三类,使用另外两个类来完成您需要的。

#2


125  

Try to set properties on module.exports, instead of replacing it completely. E.g., module.exports.instance = new ClassA() in a.js, module.exports.ClassB = ClassB in b.js. When you make circular module dependencies, the requiring module will get a reference to an incomplete module.exports from the required module, which you can add other properties latter on, but when you set the entire module.exports, you actually create a new object which the requiring module has no way to access.

尝试在模块上设置属性。出口,而不是完全替代。例如,module.exports。实例=新的ClassA()。js,module.exports。b。当您进行循环模块依赖时,需要模块将获得一个不完整模块的引用。从所需的模块导出,稍后您可以添加其他属性,但是在设置整个模块时。导出,实际上是创建一个新对象,这个对象需要模块无法访问。

#3


37  

[EDIT] it's not 2015 and most libraries (i.e. express) have made updates with better patterns so circular dependencies are no longer necessary. I recommend simply not using them.

[编辑]现在不是2015年,大多数库(比如express)已经更新了更好的模式,因此不再需要循环依赖。我建议不要使用它们。


I know I'm digging up an old answer here... The issue here is that module.exports is defined after you require ClassB. (which JohnnyHK's link shows) Circular dependencies work great in Node, they're just defined synchronously. When used properly, they actually solve a lot of common node issues (like accessing express.js app from other files)

Just make sure your necessary exports are defined before you require a file with a circular dependency.

只需确保在需要具有循环依赖项的文件之前定义了必要的导出。

This will break:

这将打破:

var ClassA = function(){};
var ClassB = require('classB'); //will require ClassA, which has no exports yet

module.exports = ClassA;

This will work:

这将工作:

var ClassA = module.exports = function(){};
var ClassB = require('classB');

I use this pattern all the time for accessing the express.js app in other files:

我一直使用这种模式来访问express。其他文件中的js app:

var express = require('express');
var app = module.exports = express();
// load in other dependencies, which can now require this file and use app

#4


25  

Sometimes it is really artificial to introduce a third class (as JohnnyHK advises), so in addition to Ianzz: If you do want to replace the module.exports, for example if you're creating a class (like the b.js file in the above example), this is possible as well, just make sure that in the file that is starting the circular require, the 'module.exports = ...' statement happens before the require statement.

有时引入第三类(正如JohnnyHK建议的那样)确实是人为的,所以除了Ianzz之外:如果您确实想要替换模块的话。导出,例如,如果您正在创建一个类(如b)。上面例子中的js文件),这也是可能的,只要确保在开始循环要求的文件中有'module。出口=…语句发生在require语句之前。

a.js (the main file run with node)

一个。js(带节点运行的主文件)

var ClassB = require("./b");

var ClassA = function() {
    this.thing = new ClassB();
    this.property = 5;
}

var a = new ClassA();

module.exports = a;

b.js

研究

var ClassB = function() {
}

ClassB.prototype.doSomethingLater() {
    util.log(a.property);
}

module.exports = ClassB;

var a = require("./a"); // <------ this is the only necessary change

#5


10  

The solution is to 'forward declare' your exports object before requiring any other controller. So if you structure all your modules like this and you won't run into any issues like that:

解决方案是在需要任何其他控制器之前'forward declare'您的export对象。所以,如果你像这样组织所有的模块,你就不会遇到这样的问题:

// Module exports forward declaration:
module.exports = {

};

// Controllers:
var other_module = require('./other_module');

// Functions:
var foo = function () {

};

// Module exports injects:
module.exports.foo = foo;

#6


7  

A solution which require minimal change is extending module.exports instead of overriding it.

一个需要最小改变的解决方案是扩展模块。出口,而不是压倒它。

a.js - app entry point and module which use method do from b.js*

一个。js - app入口点和使用方法的模块从b.js*。

_ = require('underscore'); //underscore provides extend() for shallow extend
b = require('./b'); //module `a` uses module `b`
_.extend(module.exports, {
    do: function () {
        console.log('doing a');
    }
});
b.do();//call `b.do()` which in turn will circularly call `a.do()`

b.js - module which use method do from a.js

b。js -用方法做的模块

_ = require('underscore');
a = require('./a');

_.extend(module.exports, {
    do: function(){
        console.log('doing b');
        a.do();//Call `b.do()` from `a.do()` when `a` just initalized 
    }
})

It will work and produce:

它将发挥作用并产生:

doing b
doing a

While this code will not work:

虽然该代码不能工作:

a.js

a.js

b = require('./b');
module.exports = {
    do: function () {
        console.log('doing a');
    }
};
b.do();

b.js

研究

a = require('./a');
module.exports = {
    do: function () {
        console.log('doing b');
    }
};
a.do();

Output:

输出:

node a.js
b.js:7
a.do();
    ^    
TypeError: a.do is not a function

#7


2  

What about lazy requiring only when you need to? So your b.js looks as follows

如果只在需要的时候才要求懒惰呢?所以你的b。js看起来如下所示

var ClassB = function() {
}
ClassB.prototype.doSomethingLater() {
    var a = require("./a");    //a.js has finished by now
    util.log(a.property);
}
module.exports = ClassB;

Of course it is good practice to put all require statements on top of the file. But there are occasions, where I forgive myself for picking something out of an otherwise unrelated module. Call it a hack, but sometimes this is better than introducing a further dependency, or adding an extra module or adding new structures (EventEmitter, etc)

当然,将所有require语句放在文件顶部是很好的实践。但有些时候,我原谅自己从一个不相关的模块中挑选出一些东西。称之为黑客,但有时这比引入进一步的依赖、添加额外的模块或添加新的结构(event发射器等)要好。

#8


1  

Similar to lanzz and setect's answers, I have been using the following pattern:

类似于lanzz和setect的答案,我一直使用以下模式:

module.exports = Object.assign(module.exports, {
    firstMember: ___,
    secondMember: ___,
});

The Object.assign() copies the members into the exports object that has already been given to other modules.

assign()将成员复制到已提供给其他模块的export对象中。

The = assignment is logically redundant, since it is just setting module.exports to itself, but I am using it because it helps my IDE (WebStorm) to recognise that firstMember is a property of this module, so "Go To -> Declaration" (Cmd-B) and other tooling will work from other files.

赋值在逻辑上是多余的,因为它只是设置模块。对其本身的导出,但我使用它是因为它帮助我的IDE (WebStorm)认识到firstMember是这个模块的属性,所以“Go to ->声明”(Cmd-B)和其他工具将从其他文件中工作。

This pattern is not very pretty, so I only use it when a cyclic dependency issue needs to be resolved.

这个模式不是很漂亮,所以我只在需要解决循环依赖问题时才使用它。

#9


1  

An other method I've seen people do is exporting at the first line and saving it as a local variable like this:

我看到的另一种方法是在第一行导出并将其作为本地变量保存如下:

let self = module.exports = {};

const a = require('./a');

// Exporting the necessary functions
self.func = function() { ... }

I tend to use this method, do you know about any downsides of it?

我喜欢用这种方法,你知道它有什么缺点吗?

#10


0  

Actually I ended up requiring my dependency with

事实上,我最终需要依赖

 var a = null;
 process.nextTick(()=>a=require("./a")); //Circular reference!

not pretty, but it works. It is more understandable and honest than changing b.js (for example only augmenting modules.export), which otherwise is perfect as is.

虽然不漂亮,但很管用。这比改变b更容易理解和诚实。js(例如,只增加模块。export),否则就很完美。

#11


0  

You can solve this easily: just export your data before you require anything else in modules where you use module.exports:

您可以很容易地解决这个问题:在使用模块时需要任何其他模块之前,只需导出数据。

classA.js

classA.js

class ClassA {

    constructor(){
        ClassB.someMethod();
        ClassB.anotherMethod();
    };

    static someMethod () {
        console.log( 'Class A Doing someMethod' );
    };

    static anotherMethod () {
        console.log( 'Class A Doing anotherMethod' );
    };

};

module.exports = ClassA;
var ClassB = require( "./classB.js" );

let classX = new ClassA();

classB.js

classB.js

class ClassB {

    constructor(){
        ClassA.someMethod();
        ClassA.anotherMethod();
    };

    static someMethod () {
        console.log( 'Class B Doing someMethod' );
    };

    static anotherMethod () {
        console.log( 'Class A Doing anotherMethod' );
    };

};

module.exports = ClassB;
var ClassA = require( "./classA.js" );

let classX = new ClassB();

#12


-4  

for your problem, you can use function declarations.

对于您的问题,可以使用函数声明。

class-b.js:

class-b.js:

var ClassA = require('./class-a')

module.exports = ClassB

function ClassB() {

}

class-a.js:

class-a.js:

var classB = require('./class-b')

module.exports = ClassA

function ClassA() {

}