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

时间:2012-06-03 09:42:21

标签: node.js module require cyclic-reference cyclic-dependency

我最近一直在使用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的实例。

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

15 个答案:

答案 0 :(得分:159)

尝试在module.exports上设置属性,而不是完全替换它。例如,module.exports.instance = new ClassA()中的a.jsmodule.exports.ClassB = ClassB中的b.js。当您创建循环模块依赖项时,需求模块将从所需模块中获取对不完整module.exports的引用,您可以在后面添加其他属性,但是当您设置整个module.exports时,实际上创建一个需要模块无法访问的新对象。

答案 1 :(得分:68)

虽然node.js确实允许循环require依赖项,但您发现它可以是pretty messy,并且您可能最好不要重构代码而不需要它。也许创建一个使用其他两个来完成你需要的第三个类。

答案 2 :(得分:46)

[编辑]它不是2015年,大多数图书馆(即快递)都使用更好的模式进行更新,因此不再需要循环依赖。我建议只是不使用它们

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

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

这会破坏:

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

我一直使用这种模式访问其他文件中的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

答案 3 :(得分:30)

有时引入第三类(如JohnnyHK建议的那样)是非常人为的,所以除了Ianzz之外: 如果你想要替换module.exports,例如,如果你正在创建一个类(如上例中的b.js文件),这也是可能的,只需确保在启动的文件中circular 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

答案 4 :(得分:13)

解决方案是向前宣布&#39;您需要任何其他控制器之前的导出对象。因此,如果您构建所有模块,并且您不会遇到任何类似的问题:

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

};

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

// Functions:
var foo = function () {

};

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

答案 5 :(得分:7)

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

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

虽然此代码不起作用:

<强> 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

答案 6 :(得分:5)

只有在需要的时候,懒惰需要什么?所以你的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等)更好

答案 7 :(得分:3)

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

let self = module.exports = {};

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

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

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

答案 8 :(得分:2)

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

<强> 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

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

答案 9 :(得分:1)

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

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

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

=分配在逻辑上是多余的,因为它只是将module.exports设置为自身,但我正在使用它,因为它有助于我的IDE(WebStorm)识别出firstMember是一个该模块的属性,因此“转到 - &gt;声明”(Cmd-B)和其他工具将适用于其他文件。

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

答案 10 :(得分:1)

实际上我最终要求依赖

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

不漂亮,但它确实有效。它比改变b.js更容易理解和诚实(例如只增加模块.export),否则它是完美的。

答案 11 :(得分:1)

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

在文件“ 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语句将按预期工作。

答案 12 :(得分:1)

避免这种情况的一种方法是不需要另一个文件,只需将其作为函数的参数传递给另一个文件即可。 通过这种方式,循环依赖将永远不会出现。

答案 13 :(得分:1)

TL;DR

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

扩展答案

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

让我们看看这个例子:

a.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;

ma​​in.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 接收一个空对象作为 a,然后一旦 a 完全加载,该引用通过 exports.functionA = functionA 更新。如果您改为用另一个对象替换整个模块,通过 module.exports,那么 b 将丢失来自 a 的引用,因为它将从一开始就指向同一个空对象,而不是指向新的。

因此,如果您像这样导出 amodule.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

答案 14 :(得分:0)

如果您无法消除循环依赖关系(例如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);

}

}