Node.js 中的模块化
Node 使用了 CommonJS 模块化标准。通过 exports 暴露 ,require 导入。
CommonJS 是同步的 ,会等待模块加载完后再继续执行。而 ES module 默认是异步加载的。
Node 一开始并不支持 ES module 。 现在能使用 ,不过可能需要相关配置。
需要了解可以戳这 Node.js 如何处理 ES6 模块-阮一峰
导入与导出
require
requrie
是 node 的全局方法 。在任何 js 文件里都可以使用。
它的作用有两点:
-
执行模块里的代码
-
返回模块暴露的数据
require 是同步方法 ,并且在第一次加载模块时会将结果缓存 ,第二此以后的导入时不会重新加载而是直接从缓存里拿结果。
node a.js
输出的结果是 c c3 b a c3 c2
因此我们可以看出 require
方法是同步的 。
并且 c 模块被导入了两次, c3
被输出了两次 ,但 c
和 c2
只输出一次 。因此第二次导入 c 模块时并没有去加载 c 模块而是直接从缓存里拿到结果 c3
require 的参数
require 方法接收一个参数。叫模块标识符
参数不一定是路径 , 如我们导入 http ,fs 等核心模块或者是第三方库 art-template ( 模板引擎 )
模块标识符有以下 3 种情况
- 路径
路径开头是一定带有 ./
或者 ../
的 ,这一般用来加载我们自己写的模块 (自定义模块)。
它加载时会顺着路径找到模块 ,然后执行,返回暴露的数据(没有默认就是空对象)
可以省略 .js 后缀 。
- Node 提供的核心模块(http ,fs …)
Node 的核心模块我们在下载 Node 时就下载了,它已经被放在 Node 程序里了。因此我们导入 Node 模块时 ,Node 识别后会直接加载。
- 第三方模块
第三方模块(库)需要我们提前下载好 ,下载好的第三方模块会被放到 node_module 文件夹内 。
第三方模块于 Node 的核心模块写法一样,就是它的名称。但任何第三方模块于 Node 核心模块的名称都不一样。因此如果不是核心模块和自定义模块 ,便会去 node_module 里找。
在node_module 里查找模块有点类似原型链。
也就是如果当前目录下没有 node_module . 就会去上级目录找 。一直到磁盘根目录都没有,没有就报错。
当然如果我们全局安装了。那么即使此项目里没有安装,也可以使用。
但一个项目都是有且仅有一个 node_module 文件夹。容易管理且统一存放。放在项目根目录下。项目的所有文件都可以访问。
exports
模块于模块之间的作用域是不同的 。这些模块作用域并不能相互包含。
因此即使 B 模块在 A 模块里导入 ,A模块也不能直接使用 B 模块里的变量。
如果需要把模块上的数据方法提供给其他模块使用。需要使用 exports 。其他模块可通过 require 的返回值得到。
单数据导出
module.exports=yyy
如导出一个对象 ,导出一个字符串,数组,数字。
// 对象
module.exports = {...}
// 数组
module.exports = [...]
// 字符串
module.exports = '...'
// 数值
module.exports = 123
多数据导出
-
module.exports.xxx=yyy
-
exports.xxx = yyy
// 通过 module.exports
module.exprots.num = 111
module.exports.obj = {...}
// 通过 exports
exports.num = 111
exports.obj = {...}
注意
-
module.exports.xxx= yyy
与exports.xxx=yyy
是一样的 -
单数据导出不能使用
exports = xxx
。原因我们得从它的导出原理来得出。
原理
Node 中 ,每一个 js 文件就是一个模块 。每一个模块就是一个函数 。
也就是 Node 会给文件里的代码外包一个函数 。同时添加一些必要的代码 。然后再加载时执行这个函数。
而这些函数之间没有作用域的包含。因此变量之间并没有联系。
每个模块都能访问到 Node 提供的 module 对象。就好比 在游览器里能访问 window 一样。
module 对象里有一个成员 exports
,它的值默认是一个空对象
当模块第一次被加载时,相当于执行了存放模块的函数。在全部代码执行完毕后,在最后会把 module.exports
返回 , 返回的数据作为 require
方法的返回值。
因此我们可以把需要暴露的数据放在 moduel.exports
对象里,也可以直接给它赋值。也就是单数据导出。
但如果我们暴露多条数据 ,每次都 module.exports.xxx = yyy
可得烦死。
因此在每个模块开头,又执行了一行代码(不可见)。
var exprots = module.exports
那么 exports 的地址是与 module.exports 同地址 。因此给 exports 添加成员相当于给 module.exprots 添加成员。那么多数据导出时,就不必加上 module 了。
但最后返回出去的是 module.exprots 。 这也就是说 ,如果我们直接给 exports 赋值。如 exports = '单数据'
那么就会断开 exports 于 module.exprots 的关联(同地址) 。那么最后的数据 (添加的 ‘单数据’) 这不会被导出。
模拟模块处理
function Mymodule(){
var exports = module.exports
// ..... JS 文件内的可见代码
return module.exports
}