最近在nodejs上由于一个exports使用方式方式不对导致在两个不同js循环引用的情况下导致其中一个js无法获取另外一个js的方法,从而导致执行报错,于是就去研究了一下nodeJs的循环引用。
官方给出了一个例子:
- a.js:
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');
- b.js:
console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');
- main.js:
console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done = %j, b.done = %j', a.done, b.done);
上面可以看到在a.js
中requireb.js
, b.js
中也require了a.js
,两者是循环引用, 当执行main.js的时候输出如下:
$ node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done = true, b.done = true
main.js
首先会load a.js
, 此时执行到const b = require('./b.js');
的时候,程序会转去loadb.js
, 在b.js
中执行到const a = require('./a.js');
,为了防止无限循环,将a.js
exports的未完成副本返回到b.js
模块。然后b.js
完成加载,并将其导出对象提供给a.js
模块。
我们知道nodeJs的对每个js文件进行了一层包装称为module,module中有一个属性exports,当调用require('a.js')
的时候其实返回的是module.exports对象,module.exports初始化为一个{}
空的object,所以在上面的例子中,执行到b.js
中const a = require('./a.js');
时不会load新的a module, 而是将已经load但是还未完成的a module的exports属性返回给b module,所以b.js拿到的是a module的exports对象,即:{done:false}
, 虽然在a.js
中exports.done被修改成了true,但是由于此时a.js
未load完成,所以在b.js
输出的a module的属性done为false,而在main.js
中输出的a module的属性done为true. Nodejs通过上面这种返回未完成exports对象来解决循环引用的问题。