1、场景如下
例如a.js和b.js相互引用:
a.js内容为:
exports.done = false;
var b = require('./b.js');
exports.hello = false;
console.log(`在a中b.done的值为:`+b.done);
exports.done = true;复制代码
b.js内容为:
exports.done = false;
var a = require('./a.js');
console.log(`在b中a.done的值为:`+a.done);
console.log(`在b中a.hello的值为:`+a.hello);
exports.done = true;复制代码
在main.js引用a和b
var a = require('./a.js');
var b = require('./b.js');复制代码
问题如下
1、main.js引用了a.js,此时读取a.js里的数据
2、a.js运行时先把exports.done的值设为false,接着运行第二行,第二行开始加载b.js
3、b.js运行时,先把自己的exports.done的值设为false,然后加载a.js
问题来了,此时a.js是不是又开始加载,然后又遇到b.js,b.js加载又遇到a.js。。。。无限循环呢复制代码
结果肯定是不会无限循环,我们先把结果理出来,然后分析require函数里面什么样的功能避免了无限循环引用
结果如下:
1、main.js引用了a.js,此时读取a.js里的数据
2、a.js运行时先把exports.done的值设为false,接着运行第二行,第二行开始加载b.js
3、b.js运行时,先把自己的exports.done的值设为false,然后加载a.js
4、此时的a.js不会重新加载文件,而是导出一个对象,{ exports: done }(其它属性略去)
5、所以在b.js里面var a = { exports: done }(其它属性略去),然后继续执行
6、然后b.js console出两行分别是
在b中a.done的值为:false
在b中a.hello的值为:undefined7、最后b.js把exports.done属性的值设为了true
8、接着回到a.js的第二行,var b = require('./b.js');此时b={ exports: true}(其它属性略去)
9、接着执行第三行exports.hello = false;,此时hello属性为false
10、执行第四行,打印出
在a中b.done的值为:true11、接着执行最后一行exports.done = true; 把自己的done属性又改为true复制代码
这里最让人困惑的就是上面的第四条,就是为什么a.js不会重新加载,而是导出一个对象呢
这是我之前转载的文章,关于require函数源码juejin.im/post/5c7b6e…
其中require你相当于看成如下函数(只关心第三步和第四步)
Module._load = function(request, parent, isMain) {
// 计算绝对路径
var filename = Module._resolveFilename(request, parent);
// 第一步:如果有缓存,取出缓存
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
// 第二步:是否为内置模块
if (NativeModule.exists(filename)) {
return NativeModule.require(filename);
}
// 第三步:生成模块实例,存入缓存
var module = new Module(filename, parent);
/**注:Module实现如下
*function Module(id, parent) { *this.id = id;
*this.exports = {};
*this.parent = parent;
*this.filename = null;
*this.loaded = false;
*this.children = [];
*}
**/ Module._cache[filename] = module;
// 第四步:加载模块
try {
module.load(filename);
hadException = false;
} finally {
if (hadException) {
delete Module._cache[filename];
}
}
// 第五步:输出模块的exports属性
return module.exports;
};复制代码
注意第三步是先存入缓存,后加载模块,也就是说,模块只加载一次,后面的全用缓存的了,所以之前的a.js加载过一次之后,剩下的都是用缓存里的new Module(filename, parent)了
这个new Module是一个对象,里面包括exports属性,这个exports属性就是每个模块导出的属性,例如exports.done = false
本文结束