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

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

有没有一个正确/更好的方式来构build模块来实现我想要的? 有没有更好的方式来分享模块之间的variables?

虽然node.js确实允许循环require依赖关系,但是你已经发现它可能非常混乱 ,你可能更好的重构你的代码而不需要它。 也许创build第三个类,使用其他两个来完成你所需要的。

尝试在module.exports上设置属性,而不是完全replace它。 例如, module.exports.instance = new ClassA()a.js中的module.exports.ClassB = ClassB 。 当你创build循环模块的依赖关系时,需求模块会得到一个不完整模块的引用。从需要的模块中module.exports ,你可以在后面添加其他属性,但是当你设置整个module.exports ,你实际上创build了一个新的对象需求模块无法访问。

[编辑]这不是2015年,大多数图书馆(即expression)已经更好的模式更新,所以循环依赖不再是必要的。 我build议不要使用它们


我知道我在这里挖掘一个旧的答案…这里的问题是,module.exports是您需要ClassB 定义的。 (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 

有时候,引入第三个类(如JohnnyHK所build议的)是非常人造的,所以除了Ianzz之外:如果你想replacemodule.exports,例如,如果你正在创build一个类(比如b.js文件上面的例子),这也是可能的,只要确保在开始循环要求的文件中,'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 

解决的办法是在需要任何其他控制器之前'forward declare'你的exports对象。 所以如果你像这样构build你所有的模块,你就不会遇到这样的问题:

 // Module exports forward declaration: module.exports = { }; // Controllers: var other_module = require('./other_module'); // Functions: var foo = function () { }; // Module exports injects: module.exports.foo = foo; 

一个需要最小改变的解决scheme是扩展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 

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

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

Object.assign()将成员复制到已被赋予其他模块的exports对象中。

=赋值在逻辑上是多余的,因为它只是将module.exports设置为自身,但是我正在使用它,因为它帮助我的IDE(WebStorm)识别firstMember是此模块的属性,所以“Go To – > Declaration” (Cmd-B)和其他工具将从其他文件工作。

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

懒惰只要你需要什么? 所以你的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语句放在文件的顶部是个好习惯。 但有些情况下,我原谅自己从其他不相关的模块中挑选某些东西。 称它为黑客,但有时这比引入进一步的依赖,或者增加一个额外的模块或添加新的结构(EventEmitter等)

其实我最终要求我的依赖

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

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

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

类b.js:

 var ClassA = require('./class-a') module.exports = ClassB function ClassB() { } 

类a.js:

 var classB = require('./class-b') module.exports = ClassA function ClassA() { }