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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10869276/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 15:49:44  来源:igfitidea点击:

How to deal with cyclic dependencies in Node.js

node.jsmodulerequirecyclic-referencecyclic-dependency

提问by Runcible

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)

a.js(与节点一起运行的主文件)

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

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

var a = new ClassA();

module.exports = a;

b.js

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?

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

采纳答案by JohnnyHK

While node.js does allow circular requiredependencies, as you've found it can be pretty messyand 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.

虽然 node.js 确实允许循环require依赖,但正如您发现的那样,它可能非常混乱,您最好重构代码以使其不需要它。也许创建一个使用其他两个类来完成您需要的第三个类。

回答by lanzz

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 = ClassBin b.js. When you make circular module dependencies, the requiring module will get a reference to an incomplete module.exportsfrom 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,而不是完全替换它。例如,module.exports.instance = new ClassA()a.jsmodule.exports.ClassB = ClassBb.js。当您创建循环模块依赖项时,需要的模块将从所需的模块中获得对不完整的引用module.exports,您可以在后面添加其他属性,但是当您设置整个 时module.exports,您实际上创建了一个需要模块没有的新对象访问方式。

回答by Will Stern

[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)已经使用更好的模式进行了更新,因此不再需要循环依赖。我建议不要使用它们



我知道我在这里挖掘了一个旧答案......这里的问题是 module.exports 已定义 after您需要 ClassB 之后。(JohnnyHK 的链接显示)循环依赖在 Node 中工作得很好,它们只是同步定义的。如果使用得当,它们实际上解决了很多常见的节点问题(例如appapp从其他文件访问 express.js )

Just make sure your necessary exports are defined beforeyou 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 appin other files:

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

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

回答by Coen

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:如果您确实想替换 module.exports,例如如果您正在创建一个类(如 b.js 文件中的上面的例子),这也是可能的,只要确保在开始循环要求的文件中,'module.exports = ...' 语句发生在 require 语句之前。

a.js(the main file run with node)

a.js(与节点一起运行的主文件)

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

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

var a = new ClassA();

module.exports = a;

b.js

js

var ClassB = function() {
}

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

module.exports = ClassB;

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

回答by Nicolas Gramlich

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:

解决方案是在需要任何其他控制器之前“向前声明”您的导出对象。因此,如果您像这样构建所有模块,并且不会遇到任何类似的问题:

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

};

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

// Functions:
var foo = function () {

};

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

回答by setec

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

需要最少更改的解决方案是扩展module.exports而不是覆盖它。

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

a.js - 应用程序入口点和使用 b.js 中 do 方法的模块*

_ = 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 - 使用来自 a.js 的方法 do 的模块

_ = 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

js

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

b.js

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

回答by Bence Gedai

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?

我倾向于使用这种方法,你知道它的任何缺点吗?

回答by zevero

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 areoccasions, 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 语句放在文件顶部是一种很好的做法。但在某些情况下,我原谅自己从一个不相关的模块中挑选了一些东西。称其为 hack,但有时这比引入进一步的依赖项、添加额外的模块或添加新结构(EventEmitter 等)更好

回答by Giuseppe Canale

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

您可以轻松解决这个问题:在使用 module.exports 的模块中需要任何其他内容之前,只需导出您的数据:

classA.js

类A.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

类B.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();

回答by joeytwiddle

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 exportsobject that has already been given to other modules.

Object.assign()成员复制到exports已分配给其他模块的对象中。

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

=分配在逻辑上是多余的,因为它只是设置module.exports本身,而是因为它可以帮助我的IDE(WebStorm)认识到,我使用它firstMember是这个模块的属性,所以“去- >宣言”(Cmd的-B)和其他工具将在其他文件中工作。

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

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