一、起源
1. 模块化是什么?有什么好处?
简单来说,就是将代码分割为多个部分,类似于函数对于逻辑的封装。我们将数据和方法私有化,只暴露部分属性的方法给外部调用的做法,称为模块化。
模块化可以提高代码的复用性和可维护性。
2. 基本的模块化
(1) 命名空间
var m1 = {
m1_data: xxx,
m1_fn: yyy,
}
var m2 = {
m2_data: xxx,
m2_fn: yyy,
}
可以看到通过命名空间虽然实现了基础的模块化,会但其副作用明显: 全局变量污染、模块内部的属性可以被篡改。
(2) IIFE
// module1.js
(function modular() {
var data = 1;
function fn() {
// ...
}
function _fn() {
// ...
}
window.module1 = {
data,
fn,
}
})()
<!--html-->
<script src="./module1"></script>
<script>
module1.fn();
</script>
通过IIFE的闭包来实现模块化是个不错的注意,保护了内部变量不被随意修改。
但是还是必须依赖全局变量,且脚本之间的依赖决定了维护script标签的顺序也是必要的,这在大型应用中(模块之间互相依赖的关系复杂)简直是灾难。
下面我们就来看看几个现在流行的模块化规范:
二、模块化规范
CJS
思想与特点
CJS将每个文件作为一个模块,有自己的模块作用域(IIFE)。
- CJS是运行时加载,Node环境不像浏览器环境有编译的过程。
- CJS模块的引入其实是其导出的浅拷贝,模块首次加载时会在当前module变量的exports挂载暴露的内容,对该模块的依赖,其实都是在访问该模块的module.exports属性值。
语法
模块通过exports属性挂载需要暴露的属性/方法
其他模块通过require方法引入,方法参数是模块路径
// module1.js
let primitiveData = 1;
let refData = {
val: 1,
};
function handlePrimitiveData() {
primitiveData++;
}
function handleRefData() {
refData.val++;
}
module.exports = {
primitiveData,
refData,
handlePrimitiveData,
handleRefData,
};
// main.js
const module1 = require('./module1.js');
module1.handlePrimitiveData();
module1.handleRefData();
console.log('=======>', module1.primitiveData); // 1
console.log(module1.refData, '<======='); // { val:2 }
module1模块首次被加载时,除了执行module1模块的代码,也会将module1暴露的内容挂载到module1的module.exports属性上,因为primitiveData是基本数据类型,所以实现的是值拷贝,即使我们通过handlePrimitiveData改变了模块内部的值,但是我们引入的永远只会是那份拷贝值。
从另一个角度来说,正因为refData是引用拷贝,所以我们通过handleRefData改变了模块内部的refData值后,还是可以通过引入的refData观察到。
ESM
思想与特点
ESM希望尽量的静态化,在编译时确定模块之间的依赖关系。
- ESM也用文件划分模块,模块之间基于引用完成依赖
语法
ESM模块通过export命令暴露数据,通过export.default暴露默认数据。
ESM通过import命令引入数据
// module1.js
export default {
name: 'module1.js'
}
// main.js
import m1 from './module1';
console.log(m1.name); // 'module1.js'