NodeJs
的模块化原文github
地址戳这里: github.com/wangkaiwd/n…javascript
在介绍以前,咱们能够先简单了解一下javascript
的模块化历程前端
javascript
模块化AMD
: Asynchronous Module DefinitionCMD
: Common Module DefinitionUMD
: Universal Module DefinitionCommonJS
: Node
采用该模块化方式ES6
模块化推荐文章:java
因为这里使用的是Nodejs
,因此咱们主要学习一些CMD(commonJS)
的相关内容node
NodeJs
中的模块化require
和module.exports
使用在NodeJs
中,咱们会经过module.exports
或者exports
来导出一个javascript
文件中定义的元素,而后经过require
将导出元素进行引入:git
// demo02.js
console.log('1');
module.exports = () => {
console.log('Hi, I am module demo2');
};
console.log('2');
// demo01.js
console.log('before require');
const demo2 = require('./demo02');
console.log('after require');
demo2();
复制代码
接下来咱们在当前目录中打开命令行窗口,输入node demo02.js
:github
before require
1
2
after require
Hi, I am module demo2
复制代码
因此,咱们在经过require
将一个模块导入的时候,不只能够接收模块内部经过module.exports
暴露的元素,还会执行相应模块内的js
代码缓存
接下来,咱们在demo01.js
中再加入如下代码:bash
const repeatDemo2 = require('./demo02');
repeatDemo2();
复制代码
执行后的输出结果以下:模块化
before require
1
2
after require
Hi, I am module demo2
Hi, I am module demo2
复制代码
输出结果大概告诉咱们这样一件事: 在首次引入某个模块的时候,NodeJs
会对模块内的代码进行缓存,而当咱们再次引入该模块时,会经过缓存来读取导出的内容,模块内的代码并不会从新执行。函数
咱们能够经过require.cache
属性来看到NodeJs
对模块的缓存:
// 在引入模块以前和以后分别输出require.cache
// demo03.js
console.log('before require');
console.log(require.cache);
const demo2 = require('./demo02');
console.log('after require');
console.log(require.cache);
复制代码
经过截图咱们能够很明显的看出,在require
demo02
后缓存中多了一些内容:
在阅读完上边的代码以后,这里咱们能够对require
的功能进行一个小结:
require
会引入一个模块中经过module.exports
导出的元素require
首次引入模块过程当中,会执行模块文件中的代码,并将模块文件进行缓存exports
和module.exports
咱们先看一下NodeJs
官方对exports
的定义:
exports
变量是在模块的文件级做用域内可用的,且在模块执行以前赋值给module.expors
这句话的大概意思是说: exports
并非一个全局变量,只在模块文件内有效,而且在每一个模块文件(js
文件)执行以前将module.exports
的值赋值给exports
。即至关于在每一个js
文件的开头执行了以下代码:
exports = module.exports
复制代码
这意味着exports
和module.exports
指向了同一片内存空间,当为exports
或者module.exports
从新赋值的时候,它们将再也不指向同一个引用,而咱们requie
引入的一直都是module.exports
导出的内容。
// demo04.js
// 本质上来说:exports是module.exports的一个引用,它们指向同一片内存空间
// exports = module.exports
exports.a = 1;
module.exports = { b: 2 }; // 当引用发生变化的时候,exports再也不是module.exports的快捷方式
复制代码
这时模块暴露出来的对象是{b:2}
。
官方也对这种行为进行了假设实现:
function require(/* ... */) {
// 一个全局的module对象
const module = { exports: {} };
// 这里自执行函数传参时进行了赋值: exports = module.exports
((module, exports) => {
// 模块代码在这。在这个例子中,定义了一个函数。
function someFunc() {}
exports = someFunc;
// 此时,exports 再也不是一个 module.exports 的快捷方式,
// 且这个模块依然导出一个空的默认对象。
module.exports = someFunc;
// 此时,该模块导出 someFunc,而不是默认对象。
})(module, module.exports);
// 最终导出的一直都是module.exports,只不过能够经过exports来更改它们的引用,间接的改变module.exports
return module.exports;
}
复制代码
假设咱们有这样一种场景: 模块a.js
依赖于b.js
中的某个方法,而模块b.js
也一样依赖于a.js
中的某个方法,这样的话会不会形成死循环呢?
笔者这里写了一个demo
来重现这个问题,帮助咱们更好的理解模块之间的相互引用:
// demo05.js
const demo6 = require('./demo06');
console.log('I am demo5', demo6);
module.exports = { demo5: 'demo5' };
// demo06.js
const demo5 = require('./demo05');
console.log('I am demo6', demo5);
module.exports = { demo6: 'demo6' };
复制代码
执行结果以下(咱们能够先猜一下):
I am demo6 {}
I am demo5 { demo6: 'demo6' }
复制代码
因此咱们能够得出如下执行过程:
node demo05.js
demo06.js
,而且执行demo06.js
,经过变量demo6
来接收模块demo06.js
经过module.exports
导出的对象demo06.js
的过程当中,又引入了demo05.js
,而因为demo05.js
已经执行了一部分,因为缓存缘由,并不会从新执行,此时demo05.js
中的module.exports
仍是初始值{}
。因此变量demo5
为{}
。demo05.js
在引入demo06.js
后继续执行后续代码能够看出nodeJs
对于模块之间的递归引用进行了优化,并不会引起死循环,可是须要注意的是在引入的时候要注意代码的执行顺序,不然可能会取不到对应的变量。
到这里,小伙伴在NodeJs
中使用require
进行引入以及经过module.exports
来导出文件时的执行逻辑有了更清晰的认识呢?