200

我最近一直在使用 nodejs 并且仍然掌握模块系统,如果这是一个明显的问题,我深表歉意。我想要的代码大致如下:

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

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

var ClassB = function() {
}

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

module.exports = ClassB;

我的问题似乎是我无法从 ClassB 的实例中访问 ClassA 的实例。

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

4

16 回答 16

196

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.

于 2012-06-03T18:54:03.337 回答
106

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.

于 2012-06-03T18:49:14.413 回答
60

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


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

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

这将打破:

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

module.exports = ClassA;

这将起作用:

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

我一直使用这种模式来访问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
于 2014-01-21T16:51:10.653 回答
46

有时引入第三个类(如 JohnnyHK 建议的那样)确实是人为的,所以除了 Ianzz:如果你确实想替换 module.exports,例如如果你正在创建一个类(比如上面的例子),这也是可能的,只要确保在启动循环 require 的文件中,'module.exports = ...' 语句发生在 require 语句之前。

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

var ClassB = function() {
}

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

module.exports = ClassB;

var a = require("./a"); // <------ this is the only necessary change
于 2012-12-31T06:26:17.087 回答
14

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

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

};

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

// Functions:
var foo = function () {

};

// Module exports injects:
module.exports.foo = foo;
于 2014-08-18T00:35:39.300 回答
9

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

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

类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();
于 2018-04-30T09:10:19.713 回答
8

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

a.js - 使用 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 - 使用来自 a.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 
    }
})

它将工作并产生:

doing b
doing 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();

输出:

node a.js
b.js:7
a.do();
    ^    
TypeError: a.do is not a function
于 2014-06-26T10:31:48.247 回答
8

只在需要时才需要懒惰呢?所以你的 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;

当然,将所有 require 语句放在文件顶部是一种很好的做法。但在某些情况下,我会原谅自己从一个原本不相关的模块中挑选出一些东西。称其为 hack,但有时这比引入进一步的依赖,或添加额外的模块或添加新结构(EventEmitter 等)要好

于 2017-04-17T13:36:28.410 回答
7

重要的是不要重新分配module.exports您已获得的对象,因为该对象可能已被分配给循环中的其他模块!只需在内部分配属性module.exports,其他模块就会看到它们出现。

所以一个简单的解决方案是:

module.exports.firstMember = ___;
module.exports.secondMember = ___;

唯一真正的缺点是需要重复module.exports.多次。


类似于 lanzz 和 setec 的答案,我一直在使用以下模式,感觉更具声明性:

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

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

分配在=逻辑上是多余的,因为它只是设置module.exports为自己,但我使用它是因为它帮助我的 IDE(WebStorm)识别这firstMember是该模块的属性,所以“转到 -> 声明”(Cmd-B)其他工具将适用于其他文件。

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

它非常适合于显示模式,因为您可以轻松地在对象中添加和删除导出,尤其是在使用 ES6 的属性简写时。

Object.assign(module.exports, {
    firstMember,
    //secondMember,
});
于 2017-03-03T04:06:22.490 回答
6

我见过人们做的另一种方法是在第一行导出并将其保存为局部变量,如下所示:

let self = module.exports = {};

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

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

我倾向于使用这种方法,你知道它有什么缺点吗?

于 2017-12-20T17:58:03.800 回答
4

TL;博士

只需使用exports.someMember = someMember而不是module.exports = { // new object }.

扩展答案

在阅读了 lanzz 的回复后,我终于可以弄清楚这里发生了什么,所以我会在这个问题上给我两分钱,扩展他的答案。

让我们看看这个例子:

一个.js

console.log("a starting");

console.log("a requires b");
const b = require("./b");
console.log("a gets b =", b);

function functionA() {
  console.log("function a");
}

console.log("a done");
exports.functionA = functionA;

b.js

console.log("b starting");

console.log("b requires a");
const a = require("./a");
console.log("b gets a =", a);

function functionB() {
  console.log("On b, a =", a)
}

console.log("b done");
exports.functionB = functionB;

main.js

const a = require("./a");
const b = require("./b");

b.functionB()

输出

a starting
a requires b
b starting
b requires a
b gets a = {}
b done
a gets b = { functionB: [Function: functionB] }
a done
On b, a = { functionA: [Function: functionA] }

在这里我们可以看到,首先b接收一个空对象 as a,然后一旦a完全加载,该引用就会通过 更新exports.functionA = functionA。如果您将整个模块替换为另一个对象,通过module.exports,那么b将丢失来自 的引用a,因为它将从一开始就指向同一个空对象,而不是指向新对象。

因此,如果您a像这样导出:module.exports = { functionA: functionA },那么输出将是:

a starting
a requires b
b starting
b requires a
b gets a = {}
b done
a gets b = { functionB: [Function: functionB] }
a done
On b, a = {} // same empty object
于 2021-04-01T20:15:17.363 回答
3

这是我发现使用已满的快速解决方法。

在文件“a.js”上

let B;
class A{
  constructor(){
    process.nextTick(()=>{
      B = require('./b')
    })
  } 
}
module.exports = new A();

在文件“b.js”上写下以下内容

let A;
class B{
  constructor(){
    process.nextTick(()=>{
      A = require('./a')
    })
  } 
}
module.exports = new B();

这样,事件循环类的下一次迭代将被正确定义,并且那些 require 语句将按预期工作。

于 2019-05-24T03:00:34.307 回答
3

实际上我最终需要我的依赖

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

不漂亮,但它的工作原理。它比改变 b.js(例如只增加 modules.export)更容易理解和诚实,否则它是完美的。

于 2017-04-17T14:01:40.593 回答
1

极其简单的解决方案通常是:

通常你会在文件的顶部有 require ...

var script = require('./script')
function stuff() {
      script.farfunction()
}

相反,只需要它“在函数中”

function stuff() {
      var _script = require('./script')
      _script.farfunction()
}
于 2022-01-17T12:35:04.540 回答
1

避免它的一种方法是不需要另一个文件中的一个文件,只需将它作为参数传递给函数,无论您在另一个文件中需要什么。通过这种方式,循环依赖将永远不会出现。

于 2020-04-25T14:42:35.677 回答
-4

如果你不能消除循环依赖(例如 useraccount <---> userlogin),还有一个选择......

它就像使用一样简单setTimeout()

//useraccount.js

let UserLogin = {};

setTimeout(()=>UserLogin=require('./userlogin.js'), 10);

class UserAccount{
 
getLogin(){
return new UserLogin(this.email);

}

}



//userlogin.js

let UserAccount ={};

setTimeout(()=>UserAccount=require('./useraccount.js'), 15);


class UserLogin{

getUser(){

return new User(this.token);

}

}
于 2020-12-16T00:47:11.513 回答