前端三类模块化方式理解

出现背景设计思想导入导出方式使用场景四个维度,对比分析 IIFE、CommonJS 和 ES6 Modules 的异同。


一、 IIFE(立即执行函数)

1、出现背景

  • 时代:早期前端(2000~2010 年),无模块化标准。
  • 问题:全局变量污染、脚本依赖顺序难以管理(如 jQuery 插件依赖 jQuery)。

2、设计思想

  • 核心:通过函数作用域(闭包)隔离代码,返回一个对象暴露接口。
  • 目标:减少全局污染,模拟模块的私有和公有成员。

3、导入导出方式

// 导出模块(moduleA.js)
var moduleA = (function() {
  var privateVar = '内部数据';
  return {
    publicVar: '公开数据',
    getPrivate: function() { return privateVar; }
  };
})();

// 导入模块(依赖需手动管理)
(function(moduleA) {
  console.log(moduleA.publicVar); // '公开数据'
})(moduleA);

特点

  • 导出:返回一个对象。
  • 导入:通过全局变量或参数传递依赖。

4、使用场景

  • 早期浏览器环境:如 jQuery 插件开发。
  • 小型项目:无需复杂依赖管理的场景。
  • 兼容性要求高:不支持现代模块化的旧系统。

缺点

  • 依赖需手动维护,无法动态加载。
  • 无标准化规范,难以规模化协作。

二、 CommonJS(Node.js 模块化)

1、出现背景

  • 时代:2009 年 Node.js 诞生,需服务端模块化方案。
  • 问题:浏览器端无模块化标准,服务端需同步加载文件。

2、设计思想

  • 核心:每个文件是一个模块,同步加载,通过 module.exports 导出、require 导入。
  • 目标:简单高效的模块依赖管理,适合服务端 I/O 场景。

3、导入导出方式

// 导出模块(math.js)
module.exports = {
  add: (a, b) => a + b,
  PI: 3.14
};

// 导入模块(app.js)
const math = require('./math.js');
console.log(math.add(2, 3)); // 5

特点

  • 导出:module.exportsexports.xxx
  • 导入:require 动态加载(运行时解析)。
  • 值拷贝:导出的是值的副本(原模块修改不影响已导入的值)。

4、使用场景

  • Node.js 服务端:天然支持,无需额外工具。
  • 传统前端打包:Webpack/Browserify 可将 CommonJS 转浏览器兼容代码。
  • 同步加载场景:如配置文件读取。

缺点

  • 同步阻塞:不适合浏览器直接使用(需打包解决)。
  • 动态加载:无法静态分析(不利于 Tree Shaking)。

三、 ES6 Modules(ESM)

1、出现背景

  • 时代:2015 年 ES6 标准化,解决浏览器和服务端统一模块化问题。
  • 问题:CommonJS 不适合浏览器,AMD/UMD 复杂且非原生。

2、设计思想

  • 核心:静态化模块系统,通过 export 导出、import 导入。
  • 目标:语言层面支持模块化,兼容浏览器和服务端。

3、导入导出方式

ES Modules(ESM)提供了多种灵活的导入导出方式,以下是所有可能的用法,涵盖命名导出/导入默认导出/导入混合导出动态导入等场景。

1) 导出(Export)
  • 命名导出(Named Exports)
    每个模块可以导出多个命名变量/函数/类。
// 方式1:直接导出声明
export const name = 'Alice';
export function greet() { return 'Hello!'; }
export class Person { constructor(name) { this.name = name; } }

// 方式2:先定义后统一导出
const age = 30;
const city = 'Beijing';
export { age, city };

// 方式3:导出时重命名
export { age as userAge, city as userCity };
  • 默认导出(Default Export)
    每个模块只能有一个默认导出(通常用于导出主功能)。
// 方式1:直接导出默认值
export default function() { return 'Default Function'; }

// 方式2:先定义后默认导出
const foo = { key: 'value' };
export default foo;

// 方式3:默认导出类
export default class MyClass {}
  • 混合导出(Named + Default)
export const version = '1.0';
export default function main() { return 'Main Function'; }
  • 重新导出(Re-export)
    从其他模块聚合导出(常用于封装多模块)。
// 重新导出其他模块的所有命名导出
export * from './moduleA.js';

// 重新导出其他模块的默认导出(需别名)
export { default as ModuleB } from './moduleB.js';

// 选择性重新导出
export { foo, bar as myBar } from './utils.js';

2) 导入(Import)
  • 命名导入(Named Imports)
// 基本用法
import { name, greet, Person } from './module.js';

// 导入时重命名
import { name as userName, greet as sayHello } from './module.js';

// 导入全部命名导出(绑定到对象)
import * as module from './module.js';
console.log(module.name); // 访问导出的变量
  • 默认导入(Default Import)
// 默认导入(名称可自定义)
import mainFunction from './module.js';
import MyClass from './module.js'; // 默认导出的类
  • 混合导入(Named + Default)
import defaultExport, { namedExport1, namedExport2 } from './module.js';
import defaultExport, * as namedExports from './module.js';
  • 动态导入(Dynamic Import)
    按需异步加载模块(返回 Promise)。
// 方式1:then 链式调用
import('./module.js').then(module => {
  console.log(module.default); // 默认导出
  console.log(module.namedExport); // 命名导出
});

// 方式2:async/await
async function loadModule() {
  const module = await import('./module.js');
  // 使用模块...
}
  • 副作用导入(仅执行模块,不导入内容)
import './init.js'; // 执行模块中的代码(如 polyfill 或初始化逻辑)

3) 特殊用法与注意事项

1. 导出值的绑定关系

  • ESM 导出的是动态绑定(引用),修改原模块会影响导入的值。
    // counter.js
    export let count = 0;
    export function increment() { count++; }
    
    // app.js
    import { count, increment } from './counter.js';
    console.log(count); // 0
    increment();
    console.log(count); // 1(值同步更新)
    

2. 默认导出的本质

  • 默认导出实际上是名为 default 的特殊命名导出。
    // 以下两种写法等价
    export default function foo() {}
    export { foo as default };
    

3. 浏览器直接使用 ESM

<script type="module">
  import { greet } from './module.js';
  greet();
</script>

注意

  • 需通过 HTTP 协议运行(file:// 协议会报错)。
  • 支持相对/绝对路径或完整的 URL(不支持省略扩展名)。

4) 完整示例

模块定义(math.js)

// 命名导出
export const PI = 3.14;
export function sum(a, b) { return a + b; }

// 默认导出
export default function multiply(a, b) { return a * b; }

模块使用(app.js)

// 导入默认导出和命名导出
import multiply, { PI, sum } from './math.js';

// 动态导入
const loadModule = async () => {
  const math = await import('./math.js');
  console.log(math.default(2, 3)); // 6
};

5)总结

分类语法用途
命名导出export const/function/class...导出多个变量/函数/类
默认导出export default ...导出模块的主功能
混合导出export default + export {...}同时导出默认和命名内容
重新导出export * from 'module'聚合多个模块的导出
命名导入import { x, y } from 'module'导入特定命名导出
默认导入import x from 'module'导入默认导出
动态导入import('module')按需异步加载模块

6)使用场景

  • 现代前端项目:React/Vue/Angular + Webpack/Rollup/Vite。
  • 浏览器原生支持<script type="module"> 直接使用。
  • Node.js 新项目:需配置 "type": "module".mjs 后缀。

优势

  • 静态分析:支持 Tree Shaking 优化。
  • 标准化:未来唯一模块化标准。

兼容性问题

  • 旧浏览器需通过打包工具转译。

四、三类模块化对比总结

维度IIFECommonJSES Modules
出现背景解决全局污染Node.js 服务端需求浏览器/服务端统一化
设计思想闭包隔离作用域同步加载,文件即模块静态化,语言级支持
导出方式返回对象module.exportsexport
导入方式全局变量/参数传递requireimport
加载时机同步(脚本顺序)同步(运行时)静态(编译时)
值传递对象引用值拷贝动态绑定(引用)
适用场景旧浏览器/简单项目Node.js/传统前端打包现代前端/Node.js

五、演进趋势

  1. 淘汰 IIFE:仅用于兼容旧代码或库开发(如 UMD)。
  2. CommonJS 逐步迁移:Node.js 新项目推荐 ESM,旧项目逐步迁移。
  3. ES Modules 成为未来:浏览器原生支持,工具链全面适配。

选择建议

  • 新项目一律使用 ES Modules
  • 库开发提供 ESM + CommonJS 双版本。
  • 旧项目按需迁移(如 Webpack 支持混合使用)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值