怎样可以有效的解决因为循环引用导致空对象的问题?
发布于 6 年前 作者 kenshinhu 4862 次浏览 来自 问答

今天代码遇到方法未定义has no method问题。发现是循环引用的问题。

nodejs在遇到循环require时,会把require结果得到的结果变成空对象{}。这个结果包括的是循环引用链中的每一个。

例子:

a.js console.log(‘a.js’); var b = require(’./b’); console.log(‘a+.js’); console.log(b); exports={name:‘a’}; console.log(‘a++.js’);

b.js

console.log(‘b.js’); var c = require(’./c’); console.log(‘b+.js’); console.log©; exports={name:‘b’}; console.log(‘b++.js’);

c.js

console.log(‘c.js’); var a = require(’./a’); console.log(‘c+.js’); console.log(a); exports={name:‘c’}; console.log(‘c++.js’);

然后我执行

node a.js

打印如下:

a.js b.js c.js c+.js {} c++.js b+.js {} b++.js a+.js {} a++.js

可以看到node会按引用链引用,直到发现循环引用了为止。

然后开始回溯,所有的代码都会执行到。

唯一不同的是所有循环链中require的结果都是空对象{}.

在代码编写过程中怎样可以避免这个问题的出现呢?

6 回复

不太明白你的问题,试着说一下

noderequire的常规过程就是先检查require.cache,有就返回其exports object,没有就load一遍在返回exports object且保存到require.cache

你代码这种情况,官方文档modules有提到,此时exports其实是一个unfinished未完成状态的exports object,没毛病呀

咋避免。。明白原理后,自己控制require和exports的位置呗

一般require都集中放在头部,等你require都执行完了,开始业务代码了,exports也就都挂好了

循环require通常都可以改成不循环的写法

@soda-wy

之后的解决方法是加个 setTimeOut 来延时加载部分的文件

但这个解决不太优雅

@kenshinhu

同意二楼的建议。

感觉更多可能是模块设计的不够合理。其实只要把整个循环引用链中的任一引用断开就解了。

虽然Module对象上是有loaded可以判断是否load完了,但还是觉得从设计上避免比较好。

@soda-wy 从设计上仅能这样来避免。。 但话说没有了 交叉引用感觉又没有了一个强大的写法了。。。

首先产生这个现象的原因是由于 require 模块的实现:

  • 根据全路径检查模块是否存在:
    • 如果存在返回缓存 Module 类实例的 exports 属性
    • 如果不存在,新建 Module 类实例,缓存 [全路径 - 该实例]
      • 缓存后再调用 tryModuleLoad 根据文件后缀名加载模块

所以循环引用,a.js 引用 b.js,b.js 中又引用了 a.js,那么 a.js 可以引用到完整的 b 模块, 但是 b.js 只能得到 a.js 中执行 require(‘b.js’) 之前的导出的内容: 如果没有任何导出内容就是空对象 {}

我觉得要解决它,你可以把 a.js 和 b.js 都使用到的部分抽出来放到一个公共的 c.js 里面,这样子 a 和 b 都去 require(‘c.js’),可以一定程度上解决这个问题吧

回到顶部