Node.js中的模块循环依赖及其解决
如果你想 第一时间 查看我 最新 的文章,欢迎RSS订阅我的个人博客: http:// maples7.com 。
知乎专栏将延期数天到数月不等 不完全 同步博客中的文章。
微信公众号:Chapters_Of_Maples7,只更新自己随手写的想到的只言片语或图片。
本文内容可能已经不是最新,查看原文: Node.js中的模块循环依赖及其解决
Node.js 开发一般不容易遇到真正的模块循环依赖的情况,可是当你的项目开始达到一定的复杂度之后,你很有可能在你的 Node.js 编码生涯中遇到几次。而且如果你之前没有关于这方面的意识,Debug 可能会花费不少的时间。
我在最近的项目中就遇到了这种情况,而且不能轻易通过项目架构的重构来解决。具体来说,A 文件中需要用 B 文件中某些函数,B 文件又需要用到 A 文件中的某些函数。
定义问题
实际上,Node.js 官网上就有 关于模块循环 require() 的说明 。
在官网给出的例子中,有 3 个模块:main.js、a.js、b.js。其中 main.js 有对 a.js 和 b.js的引用,而 a.js 和 b.js 又是相互引用的关系(详细情况请参阅上段末的超链接)。
官网上点出了这种模块循环的情况,并且解释清楚了原因(但并没有给出具体可行的解决方案):
When main.js loads a.js, then a.js in turn loads b.js. At that point, b.js tries to load a.js. In order to prevent an infinite loop, an unfinished copy of the a.js exports object is returned to the b.js module. b.js then finishes loading, and its exports object is provided to the a.js module.
简单说就是,为了防止模块载入的死循环,Node.js 在模块第一次载入后会把它的结果进行缓存,下一次再对它进行载入的时候会直接从缓存中取出结果。所以在这种循环依赖情形下,不会有死循环,但是却会因为缓存造成模块没有按照我们预想的那样被导出(export,详细的案例分析见下文)。
官网给出了三个模块还不是循环依赖最简单的情形。实际上,两个模块就可以很清楚的表达出这种情况。根据递归的思想,解决了最简单的情形,这一类任意大小规模的问题也就解决了一半(另一半还需要探明随着问题规模增长,问题的解将会如何变化)。
下面是一个两个模块循环依赖的问题最简情形:
A.js:
let b = require('./B');
console.log('A: before logging b');
console.log(b);
console.log('A: after logging b');
module.exports = {
A: 'this is a Object'
B.js:
let a = require('./A');
console.log('B: before logging a');
console.log(a);
console.log('B: after logging a');
module.exports = {
B: 'this is b Object'
运行 A.js,将会看到如下输出:
B: before logging a
B: after logging a
A: before logging b
{ B: 'this is b Object' }
A: after logging b
JavaScript 作为一门解释型的语言,上面的打印输出清晰的展示出了程序运行的轨迹。在这个例子中,A.js 首先 require 了 B.js, 程序进入 B.js,在 B.js 中第一行又 require 了 A.js。
如前文所述,为了避免无限循环的模块依赖,在 Node.js 运行 A.js 之后,它就被缓存了,但需要注意的是,此时缓存的仅仅是一个未完工的 A.js(an unfinished copy of the a.js)。所以在 B.jsrequire A.js 时,得到的仅仅是缓存中一个未完工的 A.js,具体来说,它并没有明确被导出的具体内容(A.js 尾端)。所以 B.js 中输出的 a 是一个空对象。
之后,B.js 顺利执行完,回到 A.js 的 require 语句之后,继续执行完成。
解决问题
想要解决这个问题有一个很简明的方法,那就是在循环依赖的每个模块中先导出自身,然后再导入其他模块(对于本文的举例来说,实际只需改动 A.js 就可以达到效果)。
话不多说,放码过来:
A.js:
module.exports = {
A: 'this is a Object'
let b = require('./B');
console.log('A: before log b');
console.log(b);
console.log('A: after log b');
B.js:
module.exports = {
B: 'this is b Object'
let a = require('./A');
console.log('B: before log a');
console.log(a);
console.log('B: after log a');
此时,在 A 和 B 中,都在 require 之前就导出了自身需要导出的模块,此时输出则是这样:
B: before log a