Appearance
模块管理
基础知识
node.js使用common.js模块管理,common.js 是2009年制定的模块标准。
你可能会奇怪为什么不使用ES6 module,因为node.js推出时javascript还没有ES6 Module。
模块特点
- 每个文件都被视为一个模块
- 使用 module.exports 导出模块,使用 require 导入模块。
- 建议将文件底部定义模块导出,这样会清楚的知道模块哪些内容被导出了
- 导入时可以使用 Js 的解构获取具体的 api
- 使用module.exports导出模块,而不是直接使用exports导出模块
模块类型
我们不能将所有功能写在一个文件中,所以项目要使用模块化管理,你可以将模块理解为一个个独立的文件。使用模块思想可以更好的组织我们的项目代码,因为模块是独立文件所以可以更好的复用代码。
nodejs中有以下几种模块类型
- 本地模块即我们自己开发的模块
- nodejs内置模块
- 从 www.npmjs.com 下载安装的第三方模块
定义模块
模块的定义非常简单,任何js文件都可以是模块。
下面我们来编写第一个模块 hj.js,他与我们的普通js文件无异,但在nodejs中他就是一个模块。
function sum(a, b) {
return a + b
}
console.log('sum.js module')
然后在 index.js 中使用 require 函数导入模块
- 模块的文件扩展名 .js 是可以省略的
- 导入的模块会自动执行
require('./hj.js')
模块目录
当不指定导入文件的路径时,node 会自动导入模块。
执行下面命令可以得到,node 会从哪些目录中尝试找到模块
console.log(module.paths)
结果为
[
'/Users/hj/code/node/node_modules',
'/Users/hj/code/node_modules',
'/Users/hj/node_modules',
'/Users/node_modules',
'/node_modules'
]
模块管理
实际开发中我们只想提供模块中的某些功能,这就需要使用 module.exports 向外部提供接口。
默认导出
下面使用 module.exports 将模块hj.js 的sum 接口向外部提供
function sum(a, b) {
return a + b
}
console.log('sum.js module')
module.exports = sum
然后在 index.js 中使用使用该模块
- 这是默认导出模块,所以可以使用任何变量来接收,
const sum
可以换为const hj
const sum = require('./hj.js')
console.log(sum(1, 3))
console.log(sum(3, 5))
作用域
每个模块文件拥有独立的作用域,下面a.js与b.js模块都定义了name变量,因为有独立作用域,所以不会被覆盖。这个概念类似于javascript的函数与块作用域。
- 使用模块作用域,就不用担心模块中同名变量或函数的冲突问题
a.js
const name = 'a.js'
console.log(name)
b.js
const name = 'b.js'
console.log(name)
index.js
require('./a.js')
require('./b.js')
输出结果为
a.js
b.js
包装函数
其实node.js会将模块放在以下函数中,这就是为什么我们可以在模块文件中使用module等功能。
(function(exports,require,module,__filename,__dirname){
//模块文件代码
})
模块缓存
Commonjs 加载的模块会被缓存起来,再有文件使用该模块时将从缓存中获取
console.log(require.cache)
下例中的hj.js模块被index.js第一次require时就会缓存了,在第二次require时直接使用缓存的模块,所以两次打印结果都是 banmashou.com。
hj.js
class Hj {
name = '斑马兽'
setName(name) {
this.name = name
}
getName() {
return this.name
}
}
module.exports = new Hj()
index.js
const obj1 = require('./hj.js')
obj1.setName('banmashou.com')
console.log(obj1.getName())
const obj2 = require('./hj.js')
console.log(obj2.getName())
输出结果
banmashou.com
banmashou.com
你可以使用 vscode 的断点调试,更直观的体验到结果
- 第二次的require,在使用单步进入时并不会进入hj.js内部,只有第一次的require会进入hj.js
- 同时可以在变量监控中查看到缓存的模块
为了解决上面的问题,hj.js不要导出对象实例,而是单独的类
class Hj {
name = '斑马兽'
setName(name) {
this.name = name
}
getName() {
return this.name
}
}
module.exports = Hj
然后在index.js中使用时new出不同的实例即可
const Hj = require('./hj.js')
const obj1 = new Hj()
obj1.setName('obj1')
console.log(obj1.getName())
const obj2 = new Hj()
console.log(obj2.getName())
导出方式
我们可以有多种方式导出模块
直接导出
下面是直接将函数导出
module.exports = (a, b) => a + b
属性导出
通过exports属性导出
module.exports.sum = (a, b) => a + b
使用的时候要像这样
const hj = require('./hj')
console.log(hj.sum(1, 4))
对象导出
也可以导出的接口放在对象中统一导出
const sum = (a, b) => a + b
const webname='banmashou.com'
module.exports ={
sum,
webname
}
使用的时候可以使用结构语法获取接口
const { webname, sum } = require('./hj')
console.log(sum(1, 4), webname)
module.exports 与exports
通过对模块的包装函数理解,最终模块导出使用的是module.exports对象
(function(exports,require,module,__filename,__dirname){
//模块文件代码
})
所以我们可以简化导出,省略掉 module 前缀
const sum = (a, b) => a + b
exports.sum = sum
使用时也没有区别
const { sum } = require('./hj')
console.log(sum(1, 4))
但是因为node.js最终导出是使用module.exports对象的,如果直接使用exports导出一个对象,这时exports变量就不与module.exports使用相同的内存引用,就不会导出成功。
下面的写法将不会正确导出
const sum = (a, b) => a + b
exports = { sum }
而应用使用这样,因为nodejs内部最终使用的是module.exports变量
const sum = (a, b) => a + b
module.exports = { sum }
你可以使用 vscode 的断点调试查看到清晰的结果
JSON
common.js可以支持JSON文件的导入,下面是hj.json的内容
{
"name": "斑马兽",
"url": "https://www.banmashou.com"
}
在index.js导入使用
const data = require('./hj.json')
console.log(data)
输出结果
{ name: '斑马兽', url: 'https://www.banmashou.com' }
ES6 Module
早期javascript没有模块功能,所以node.js使用了common.js,不过从ES 2015推出了Js 模块标准简称ESM,NodeJs 13开始支持了ES6 Module。使用 ES6 模块标准,可以让我们在编写 Node、Vue、React 使用统一的模块操作方法。
下面定义 hj.mjs 支持ESM的模块文件
const sum = (a, b) => a + b
export default sum
然后在 index.mjs中使用ESM语法导入模块
import sum from './hj.mjs'
console.log(sum(4, 2))
要使用ES6 模块管理请在 package.json 定义 type 属性。
- 如果编写的是 .ts 文件,就不要设置 type 属性
{
"type": "module",
...
}
读取JSON
读取JSON文件需要在tsconfig.json中定义 resolveJsonModule 选项
{
"compilerOptions": {
...
"resolveJsonModule": true
},
"include": ["./**/*"]
}
这样我们就可以在文件中引入JSON了
import data from './hj.json'
第三方模块
我们在开发时不可能编写所有的功能,所以要使用包管理工具,安装 npmjs.com 网站上的包。
当安装 Node.js 后已经内置了 npm 这个包管理命令,我们可以使用 Npm 下载、删除、更新、发布软件包。当然也可以使用yarn 或 pnpm 命令管理第三方扩展包,有关这些使用的使用在斑马兽文档库已经有介绍。