Appearance
JavaScript 模块化规范
模块化概述
什么是模块化?
- 将程序文件依据一定规则
拆分
成多个文件,这种编码方式就是模块化
的编码方式。 - 拆分出来的
每个文件就是一个模块
,模块中的数据都是私有的
,模块之间互相隔离
。 - 也可以通过一些手段,把模块内的指定数据“
交出去
”,供其他模块使用。
为什么需要模块化?
随着应用的复杂度越来越高,代码量和文件数量急剧增加,会逐渐引发以下问题:
- 全局污染问题
- 依赖混乱问题
- 数据安全问题
有哪些模块化规范?
💡 历史背景(了解即可):2009 年,随者 Node.js 的出现,JavaScript 在服务器端的应用逐渐增多,为了让 Node.js 的代码更好维护,就必须要制定一种 Node.js 环境下的模块化规范,来自 Mozilla 的工程师 Kevin Dangoor 提出了 CommonJS 规范(CommonJS 初期的名字叫 ServerJS),随后 Nodejs 社区采纳了这一规范。
随着时间的推移,针对 JavaScript 的不同运行环境,相继出现了多种模块化规范:
CommonJS
------- 服务端应用广泛- AMD
- CMD
ES6 模块化
--------- 浏览器端应用广泛
导入与导出的概念
模块化的核心思想:模块之间是隔离
的,通过导入
与导出
进行数据和功能共享。
导出(暴露):模块公开其内部的一部分(如变量、函数等),使其可以被其他模块使用。
导入(引入):模块引入并使用其他模块导出的内容,以重用代码和功能。
CommonJS 规范
完整示例
- 导出school.js模块
js
const name = '尚硅谷'
const slogan = '让天下没有难学的技术!'
function getTel() {
return '010-56253825'
}
function getCities() {
return ['北京', '上海', '深圳', '成都', '武汉', '西安']
}
// 1.通过给exports对象添加属性的方式, 来导出数据(注意: 此处没有导出getCities)
exports.name = name
exports.slogan = slogan
exports.getTel = getTel
// 2.或者使用module.exports
module.exports.name = name
module.exports.slogan = slogan
module.exports.getTel = getTel
// 3.推荐使用以下写法
module.exports = {
name,
slogan,
getTel,
}
- 在index.js导入
js
// 1.引入school暴露的所有内容
const school = require('./school')
// 2.引入同时解构要用的数据
const { name, slogan, getTel } = require('./school')
// 3.引入同时解构+重命名
const { name: stuName, motto, getTel: stuTel } = require('./student')
导出数据
在 CommonJs 标准中,导出数据有两种方式
- 第一种:
module.exports = value
- 第二种:
exports.name = value
⚠️ 注意事项:
- 每个模块内部的
this
、exports
、module.exports
初始时都指向同一个空对象{}
, 该空对象就是当前模块导出的数据。- 最终导出的数据由
module.exports
决定。- 不能使用
exports = value
,只能使用module.exports = value
。export 是对 module.exports 的初始引用,仅为了方便给导出对象添加属性,所以不能使用 exports = value
导入数据
在CJS
模块化标准中,使用内置的require
函数进行导入数据
js
// 1.直接引入模块
const school = require('./school')
// 2.引入同时解构要用的数据
const { name, slogan, getTel } = require('./school')
// 3.引入同时解构+重命名
const { name: stuName, motto, getTel: stuTel } = require('./student')
扩展理解
CJS 模块执行时被包裹在如下内置函数
中, 每个模块都有自己的作用域:
js
function (exports, require, module, __filename, __dirname){
// 模块代码
}
ES6 模块化规范
完整示例
- 导出school.js模块
js
// 1.分别导出
export const name = { str: '尚硅谷' }
export const slogan = '让天下没有难学的技术!'
export function getTel() {
return '010-56253825'
}
const name = { str: '尚硅谷' }
const slogan = '让天下没有难学的技术!'
function getTel() {
return '010-56253825'
}
// 2.统一导出
export { name, slogan, getTel }
// 3.默认导出
export default { name, slogan, getTel }
- 在index.js导入
js
// 1.万能导入: 将模块中的所有导出内容整合到一个对象中
import * as school from './school.js'
// 2.命名导入(对应分别导出和统一导出使用),使用as可重命名
import { name, slogan, getTel } from './school.js'
import { name as myName } from './school.js'
// 3.默认导入(对应默认导出使用):这里student的名字可随意修改
import student from './student.js'
// 4.混合导入: 默认和命名导入混合
import getTel, { name, slogan } from './school.js'
// 5.动态导入
const school = await import('./school.js')
// 6.import可以不导入任何数据,只参与运行(只执行mock.js中的代码)
import './mock.js'
- 在HTML中
html
<script type="module" src="./index.js"></script>
Node 中运行 ES6 模块
方法 1: 改扩展名为
.mjs
, Node 会自动识别 ES6 模块方法 2: 在
package.json
中设置"type": "module"
导出数据
- 分别导出
export name
- 统一导出
export {name, name2}
- 默认导出(default)
export default{name, name2}
, 每个模块只能有 1 个默认导出
示例:
js
// 1.分别导出
export const name = { str: '尚硅谷' }
export const slogan = '让天下没有难学的技术!'
export function getTel() {
return '010-56253825'
}
const name = { str: '尚硅谷' }
const slogan = '让天下没有难学的技术!'
function getTel() {
return '010-56253825'
}
// 2.统一导出
export { name, slogan, getTel }
// 3.默认导出
export default { name, slogan, getTel }
⚠️ 注意:上述多种导出方式, 可以同时使用
导入数据
js
// 1.万能导入: 将模块中的所有导出内容整合到一个对象中
import * as school from './school.js'
// 2.命名导入(对应分别导出和统一导出使用),使用as可重命名
import { name, slogan, getTel } from './school.js'
import { name as myName } from './school.js'
// 3.默认导入(对应默认导出使用):这里student的名字可随意修改
import student from './student.js'
// 4.混合导入: 默认和命名导入混合
import getTel, { name, slogan } from './school.js'
// 5.动态导入
const school = await import('./school.js')
// 6.import可以不导入任何数据,只参与运行(只执行mock.js中的代码)
import './mock.js'
数据引用问题
js
// CommonJS
let sum = 1
function increment() {
sum += 1
}
module.exports = { sum, increment }
// ES6
const sum = 1
function increment() {
sum += 1
}
export { sum, increment }
⚠️ 使用原则:
ES6 模块导出的常量,务必用
const
定义否则引入模块后可以更改模块的变量的值,因为共用的一个内存空间,而不是像 CJS 模块是复制的值
CJS 和 ES6 一览表
导出 | CommonJS(CJS) | ES6 模块 |
---|---|---|
单个变量或函数 | exports.name = value 或 module.exports.name = value | export const name = value``export function getTel() {} |
多个成员 | 逐个挂在 exports 上(重复 单个导出) | 统一导出:export { name, getTel } |
整个对象或函数 | module.exports = value (直接替换整个导出对象) | 默认导出:export default value |
导入 | CommonJS(CJS) | ES6 模块 |
---|---|---|
整个模块 | const mod = require('./mod') | import * as mod from './mod.js' |
指定成员(命名导入) | 使用对象解构:const { name, getTel } = require('./mod') | import { name, getTel } from './mod.js' |
默认导出 | 不适用,CJS 没有默认导出概念 | import value from './mod.js' |
混合导入(默认 + 命名) | 不支持 | import defaultExport, { named1, named2 } from './mod.js' |
动态导入 | 可以用 require() 写在函数体中实现动态加载 | const mod = await import('./mod.js') (返回 Promise) |
.js 和.mjs .ts 和.mts 的区别
文件类型 | 模块系统 | 常用于 | 是否推荐 |
---|---|---|---|
.js | CommonJS or ESM(看配置) | Node.js、前端脚本 | ✅ 常规使用 |
.mjs | 强制 ESM | Node.js 中用 import/export | ✅ 推荐(ESM 模块) |
.ts | TypeScript(模块类型看配置) | 开发时最常用 | ✅ 常用 |
.mts | TypeScript + ESM | TypeScript 项目中使用 ESM | ✅ 推荐(明确意图) |