模块
- 允许代码分离,将其组织为可维护的单元,提升代码的可复用性和可读性;
- CommonJS(CJS)、ECMAScript Modules(ESM)
CJS模块系统
- 导出模块 只需要使用module.exports或exports将模块中的内容导出即可
- module.exports,
// 指定属性导出
module.exports.byebye = function (name) {
console.log(`byebye, ${name}!`)
}
module.exports.userInfo = {
name: 'dd',
age: 18
}
// 统一的对象导出
// 在模块中定义一个byebye函数
function byebye(name) {
console.log(`byebye, ${name}!`)
}
// 定义了一个名为 userInfo 的对象,包含姓名和年龄两个属性
const userInfo = {
name: 'dd',
age: 18
}
// 将函数和对象统一导出到模块的外部
module.exports = {
byebye,
userInfo
}
- exports
// 定义一个模块,使用 exports 导出模块中的内容。
// 定义了一个名为 byebye 的函数,用于输出告别语
// 定义了一个名为 userInfo 的对象,包含姓名和年龄两个属性
exports.byebye = function (name) {
console.log(`byebye, ${name}!`)
}
exports.userInfo = {
name: 'dd',
age: 18
}
- module.exports vs exports
module.exports 和 exports 都可用于导出 Node.js 模块中的代码的对象;
exports实际上是module.exports的一个引用,当使用exports导出模块代码时,实际上是在向module.exports添加属性
// 导出一个名为 "hello" 的函数到 "exports" 对象中
// 函数中会将 "Hello World!" 的信息输出到控制台中
exports.hello = function() {
console.log("Hello World!");
};
// 导出一个名为 "hello" 的函数到 "exports" 对象中
// 函数中会将 "Hello World!" 的信息输出到控制台中
module.exports.hello = function() {
console.log("Hello World!");
};
// 建议在写 Node.js 模块时,只使用 module.exports 导出模块代码,而不要使用 exports。如果你需要向外部导出多个函数或对象,可以将它们作为 module.exports 的属性导出;
// 外部就可以通过 require 方法导入该模块,并访问其中的 hello 和 bye 方法了。
module.exports = {
hello: function() {
console.log("Hello World!");
},
bye: function() {
console.log("Goodbye World!");
}
};
- 引入模块
(1)完整引入
// 导入模块 "./exports" 并将其赋值给变量 context
const context = require('./exports')
// 调用 context 模块中的 hello 函数,并传入 context.userInfo.name 参数
context.hello(context.userInfo.name)
(2)解构引入
// 当导出内容是一个对象时,可以使用解构引入。
// 导入模块 "./exports" 中的 hello, userInfo 和 byebye,并赋值给相应的变量
const { hello, userInfo, byebye } = require('./exports')
// 调用 hello 函数,并传入 userInfo.name 参数
hello(userInfo.name)
ES Modules模块系统(ESM)
- import/export
- 默认情况下,.js文件识别为CJS模块;如果需要识别为ES模块,有2种方式:1. 使用.mjs命名;2. package.json种的type字段设置为module;
// package.json
{
"type":"module"
}
- 默认导入导出,使用export default (export default,import xx from 'module');
// 文件 export_default.js
// 导出默认对象
export default {
// 定义 byebye 方法,输出道别信息
byebye(name) {
console.log(`byebye, ${name}!`)
},
// 定义 userInfo 对象,存储用户信息
userInfo: {
name: 'dd', // 用户名
age: 18 // 用户年龄
}
}
// 文件 import_default.js
// 引入 export_default.js 中默认导出的模块
import defaultModule from './export_default.js'
// 调用 defaultModule 中定义的 byebye() 方法,输出道别信息并传入用户姓名
defaultModule.byebye(defaultModule.userInfo.name)
- 具名导入导出,使用export (export xx,import { xx } from 'module');
// 文件 export.js
// 定义 byebye 方法,输出道别信息
export function byebye(name) {
console.log(`byebye, ${name}!`)
}
// 定义 userInfo 对象,存储用户信息
export const userInfo = {
name: 'dd', // 用户名
age: 18 // 用户年龄
}
// 文件 import.js
// 引入 export_named.js 中具名导出的模块,使用 as 关键字还可以修改导入内容的名称
import { byebye, hello, userInfo as user } from './export_named.js'
// 调用 byebye() 方法,输出道别信息并传入用户姓名
byebye(user.name)
- 导入导出所有对象,可以将另一个模块的内容直接全部导出,导出同时也可以设置默认导出; (export *,import * as xx from 'module');
// 文件 export_all.js
// 从 export.js 中导出所有的模块成员
export * from './export.js'
// 导出一个默认模块,对象包含 goal 属性,初始值为 'learn'
export default {
goal: 'learn'
}
// 文件 import_all.js
// 导入 export_all.js 中所有被导出的模块成员,并作为 allValues 对象的属性
import * as allValues from './export_all.js'
// 在控制台输出 allValues 对象
console.log(allValues)
// 从 allValues 对象中解构出 hello、byebye、default 和 userInfo 模块成员
const { byebye, default: data, userInfo } = allValues
// 调用 byebye() 方法,输出道别信息并传入用户姓名
byebye(userInfo.name)
// 输出 data 对象的 goal 属性
console.log(data.goal)
- 重新导出 (export { xx } from 'module',export * from 'module')
// lib.js
export function hello(name) {
console.log(`Hello, ${name}!`)
}
export default {
filename: 'lib.js',
des: 'lib.js的一些默认导出'
}
// util.js
export function byebye(name) {
console.log(`ByeBye, ${name}!`)
}
export default {
filename: 'util.js',
des: 'util.js的一些默认导出'
}
// index.js
// 从 './lib.js' 中导出 hello 和默认导出并重命名为 libData
export { hello, default as libData } from './lib.js'
// 从 './util.js' 中导出所有命名导出
export * from './util.js'
// 从 './util.js' 中默认导出并重命名为 utilData
export { default as utilData } from './util.js'
// 使用的时候,就统一通过 index.js 引入即可。
// usage.js
import { hello, byebye, libData, utilData } from './index.js'
hello(libData.filename)
byebye(utilData.filename)
CJS vs ESM
-
模块加载时机
CJS支持动态加载模块,require语句可以出现在任意位置;CJS 需要等到代码运行时才能确定依赖关系和加载模块;
ESM会在所有模块都加载完毕后才执行代码,在使用导入的内容之前导入;ESM 可以在代码执行前进行静态分析和优化,从而提高性能; -
导出内容的区别
ESM 中,当我们导入一个变量时,实际上是导入了该变量的引用。这意味着,如果导出的变量在导入模块中发生了改变,导入的变量也会随之改变。
CommonJS 中,导入的是导出模块的值的拷贝,而不是引用。这意味着,即使导出模块中的值发生了改变,导入模块中导入的变量不会受到影响。
ESM 导入的是值的引用,而 CJS 导入的是值的拷贝。
- 文件命名
模块一般都以 .js 结尾,通过 package.json 中 "type":"module" 区分模块类型;
还可通过文件命名来区分 .cjs 表明是 CJS 规范的模块,.mjs 表明是 ESM 规范的模块。