如何处理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() { }