一、为什么需要模块化?
想象一下你正在写一个大型项目, 所有代码都堆在一个文件里:
// 可怕的全局变量地狱var utils = {...};var api = {...};var componentA = {...};var componentB = {...};// 20000行之后...function doSomething() {//谁知道这个函数会不会覆盖前面的定义?}
这种写法有几个致命问题:
-
命名冲突: 变量和函数都在全局作用域,容易覆盖
-
依赖混乱:不知道代码执行前需要加载哪些依赖
-
难以维护: 代码组织像意大利面条,牵一发而动全身
模块化就是为了解决这些问题而生的! 它让我们可以:
-
将代码拆分成独立的小文件(模块)
-
明确声明依赖关系
-
避免污染全局命名空间
-
方便代码复用和维护
二、CommonJS
CommonJS 是 Node.js 采用的模块系统, 语法非常简单:
使用的时候如下所示:// math.jsfunction add(a, b) {return a + b;}function subtract(a, b) {return a - b;}//导出模块module.exports = {add,subtract};//或者也可以这样导出exports.add = add;exports.subtract = subtract;
它有什么特点呢?// app.jsconst math = require('./math.js');console.log(math.add(2, 3)); // 5console.log(math.subtract(5, 2)); // 3
-
同步加载: 模块在 require 时同步加载并执行.
-
缓存机制: 模块首次加载后会被缓存, 后续 require 直接返回缓存.
-
值拷贝: 导出的是值的拷贝(对于原始类型), 修改不会影响原模块.
三、ES Modules
ES Modules(简称 ESM)是 ECMAScript 2015(ES6)引入的官方模块系统, 现在已经被现代浏览器和 Node.js 支持.
基本语法:导入模块:// math.jsexport function add(a, b) {return a + b;}export function subtract(a, b) {return a - b;}// 或者默认导出export default {add,subtract};
与CommonJS的区别:// 命名导入import { add, subtract } from './math.js';// 默认导入import math from './math.js';// 混合导入import math, { add } from './math.js';// 整体导入import * as math from './math.js';
-
静态分析: ESM 的 import/export 必须在顶层作用域, 可以被静态分析
-
动态绑定: ESM 导出的是值的引用(活的绑定), 修改会影响所有导入
-
异步加载: ESM 在浏览器中是异步加载的
// counter.jsexport let count = 0;export function increment() {count++;}// app.jsimport { count, increment } from './counter.js';console.log(count); // 0increment();console.log(count); //1(ESM是动态绑定)
在浏览器里如何加载模块呢?
<!-- 传统脚本 --><script src="app.js"></script><!-- 模块脚本 --><script type="module" src="app.js"></script>
关键区别:
-
模块默认defer, 在文档解析完成后执行
-
模块有独立作用域, 不会污染全局
-
模块需要 CORS 配置, 不能使用 file:// 协议
-
模块会自动应用严格模式
四、按需加载
静态导入(import ... from ...)必须在模块顶层, 但有时候我们需要按需加载:
条件加载:// 普通导入import('./math.js').then(math => {console.log(math.add(2, 3));});// 配合 async/awaitasync function calculate() {const math = await import('./math.js');console.log(math.add(2, 3));}
预加载:if (user.isAdmin) {import('./admin.js').then(...);}
<link rel="modulepreload" href="critical-module.js">
组合使用:
Promise.all([import('./moduleA.js'),import('./moduleB.js')]).then(([moduleA, moduleB]) => {// 两个模块都加载完成});
| Austin Liu 刘恒辉 Project Manager and Software Designer E-Mail:lzhdim@163.com Blog:https://lzhdim.cnblogs.com 欢迎收藏和转载此博客中的博文,但是请注明出处,给笔者一个与大家交流的空间。谢谢大家。 |