Skip to content

JavaScript 模块化规范

模块化概述

什么是模块化?

  • 将程序文件依据一定规则拆分成多个文件,这种编码方式就是模块化的编码方式。
  • 拆分出来的每个文件就是一个模块,模块中的数据都是私有的,模块之间互相隔离
  • 也可以通过一些手段,把模块内的指定数据“交出去”,供其他模块使用。

为什么需要模块化?

随着应用的复杂度越来越高,代码量和文件数量急剧增加,会逐渐引发以下问题:

  1. 全局污染问题
  2. 依赖混乱问题
  3. 数据安全问题

有哪些模块化规范?

💡 历史背景(了解即可):2009 年,随者 Node.js 的出现,JavaScript 在服务器端的应用逐渐增多,为了让 Node.js 的代码更好维护,就必须要制定一种 Node.js 环境下的模块化规范,来自 Mozilla 的工程师 Kevin Dangoor 提出了 CommonJS 规范(CommonJS 初期的名字叫 ServerJS),随后 Nodejs 社区采纳了这一规范。

随着时间的推移,针对 JavaScript 的不同运行环境,相继出现了多种模块化规范:

  1. CommonJS ------- 服务端应用广泛
  2. AMD
  3. CMD
  4. ES6 模块化 --------- 浏览器端应用广泛

导入与导出的概念

模块化的核心思想:模块之间是隔离的,通过导入导出进行数据和功能共享。

  • 导出(暴露):模块公开其内部的一部分(如变量、函数等),使其可以被其他模块使用。

  • 导入(引入):模块引入并使用其他模块导出的内容,以重用代码和功能。

    74937006512

CommonJS 规范

完整示例

  1. 导出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,
}
  1. 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

⚠️ 注意事项:

  1. 每个模块内部的thisexportsmodule.exports 初始时都指向同一个空对象{}, 该空对象就是当前模块导出的数据。
  2. 最终导出的数据由 module.exports 决定。
  3. 不能使用 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 模块化规范

完整示例

  1. 导出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 }
  1. 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'
  1. HTML
html
<script type="module" src="./index.js"></script>

Node 中运行 ES6 模块

  • 方法 1: 改扩展名为 .mjs , Node 会自动识别 ES6 模块

  • 方法 2: 在 package.json 中设置 "type": "module"

导出数据

  1. 分别导出 export name
  2. 统一导出 export {name, name2}
  3. 默认导出(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 = valuemodule.exports.name = valueexport 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 的区别

文件类型模块系统常用于是否推荐
.jsCommonJS or ESM(看配置)Node.js、前端脚本✅ 常规使用
.mjs强制 ESMNode.js 中用 import/export✅ 推荐(ESM 模块)
.tsTypeScript(模块类型看配置)开发时最常用✅ 常用
.mtsTypeScript + ESMTypeScript 项目中使用 ESM✅ 推荐(明确意图)