Skip to content

模块管理

基础知识

向军大叔每晚八点在 抖音bilibli 直播

xj-small

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文件都可以是模块。

下面我们来编写第一个模块 hd.js,他与我们的普通js文件无异,但在nodejs中他就是一个模块。

function sum(a, b) {
  return a + b
}

console.log('sum.js module')

然后在 index.js 中使用 require 函数导入模块

  • 模块的文件扩展名 .js 是可以省略的
  • 导入的模块会自动执行
require('./hd.js')

模块目录

当不指定导入文件的路径时,node 会自动导入模块。

执行下面命令可以得到,node 会从哪些目录中尝试找到模块

console.log(module.paths)

结果为

[
  '/Users/hd/code/node/node_modules',
  '/Users/hd/code/node_modules',
  '/Users/hd/node_modules',
  '/Users/node_modules',
  '/node_modules'
]

模块管理

实际开发中我们只想提供模块中的某些功能,这就需要使用 module.exports 向外部提供接口。

默认导出

下面使用 module.exports 将模块hd.js 的sum 接口向外部提供

function sum(a, b) {
  return a + b
}

console.log('sum.js module')

module.exports = sum

然后在 index.js 中使用使用该模块

  • 这是默认导出模块,所以可以使用任何变量来接收,const sum 可以换为 const hd
const sum = require('./hd.js')

console.log(sum(1, 3))
console.log(sum(3, 5))

作用域

每个模块文件拥有独立的作用域,下面a.js与b.js模块都定义了name变量,因为有独立作用域,所以不会被覆盖。这个概念类似于javascript的函数与块作用域,我已经在后盾人录制了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)

下例中的hd.js模块被index.js第一次require时就会缓存了,在第二次require时直接使用缓存的模块,所以两次打印结果都是 houdunren.com。

hd.js

class Hd {
  name = '向军大叔'

  setName(name) {
    this.name = name
  }
  getName() {
    return this.name
  }
}

module.exports = new Hd()

index.js

const obj1 = require('./hd.js')

obj1.setName('houdunren.com')
console.log(obj1.getName())

const obj2 = require('./hd.js')
console.log(obj2.getName())

输出结果

houdunren.com
houdunren.com

你可以使用 vscode 的断点调试,更直观的体验到结果

  • 第二次的require,在使用单步进入时并不会进入hd.js内部,只有第一次的require会进入hd.js
  • 同时可以在变量监控中查看到缓存的模块
image-20230109163241811

为了解决上面的问题,hd.js不要导出对象实例,而是单独的类

class Hd {
  name = '向军大叔'

  setName(name) {
    this.name = name
  }
  getName() {
    return this.name
  }
}

module.exports = Hd

然后在index.js中使用时new出不同的实例即可

const Hd = require('./hd.js')

const obj1 = new Hd()
obj1.setName('obj1')
console.log(obj1.getName())

const obj2 = new Hd()
console.log(obj2.getName())

导出方式

我们可以有多种方式导出模块

直接导出

下面是直接将函数导出

module.exports = (a, b) => a + b

属性导出

通过exports属性导出

module.exports.sum = (a, b) => a + b

使用的时候要像这样

const hd = require('./hd')
console.log(hd.sum(1, 4))

对象导出

也可以导出的接口放在对象中统一导出

const sum = (a, b) => a + b
const webname='houdunren.com'
module.exports ={
  sum,
  webname
}

使用的时候可以使用结构语法获取接口

const { webname, sum } = require('./hd')
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('./hd')
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 的断点调试查看到清晰的结果

image-20230109171037093

JSON

common.js可以支持JSON文件的导入,下面是hd.json的内容

{
	"name": "后盾人",
	"url": "https://www.houdunren.com"
}

在index.js导入使用

const data = require('./hd.json')
console.log(data)

输出结果

{ name: '后盾人', url: 'https://www.houdunren.com' }

ES6 Module

早期javascript没有模块功能,所以node.js使用了common.js,不过从ES 2015推出了Js 模块标准简称ESM,NodeJs 13开始支持了ES6 Module。使用 ES6 模块标准,可以让我们在编写 Node、Vue、React 使用统一的模块操作方法。

下面定义 hd.mjs 支持ESM的模块文件

const sum = (a, b) => a + b

export default sum

然后在 index.mjs中使用ESM语法导入模块

import sum from './hd.mjs'

console.log(sum(4, 2))

要使用ES6 模块管理请在 package.json 定义 type 属性。

  • 如果编写的是 .ts 文件,就不要设置 type 属性
{
	"type": "module",
	...
}

ES6 Module 模块视频,向军大叔已经录制完了,你可以访问 后盾人 观看学习。

读取JSON

读取JSON文件需要在tsconfig.json中定义 resolveJsonModule 选项

{
  "compilerOptions": {
    ...
    "resolveJsonModule": true
  },
  "include": ["./**/*"]
}

这样我们就可以在文件中引入JSON了

import data from './hd.json'

第三方模块

我们在开发时不可能编写所有的功能,所以要使用包管理工具,安装 npmjs.com 网站上的包。

当安装 Node.js 后已经内置了 npm 这个包管理命令,我们可以使用 Npm 下载、删除、更新、发布软件包。当然也可以使用yarn 或 pnpm 命令管理第三方扩展包,有关这些使用的使用在后盾人文档库已经有介绍。