前言
什么是模块化
模块化简单说就是为了解决代码没有自己的"域",很容易造成命名冲突,依赖混乱。我们的代码通过模块化按照一定的规范和方式进行划分和组织,以便于模块的复用、维护和管理。
模块化解决方案
从最开始的通过立即执行函数(IIFE)到 CJS、AMD、CMD、UMD、再到 ES6 引入模块化 ESM,本文主要写 CJS 和 ESM。
CommonJs
实现原理
我们先创建两个文件
src/index.js
js
const name = require("./name")
console.log(name)
src/name.js
js
var name = "john";
module.exports = {
name: name,
}
我们把以上代码打包,通过产物看一下 CJS 到底是如何进行模块化的。产物代码经过简化。

从产物中不难看出 CJS 把每个路径的源码都存在一起,然后通过 require 来实现读取并缓存,实现模块化的功能,其实说到底还是使用了立即执行函数(IIFE)来实现的。
EsModule
我们在 name.js 加上默认导出。
js
// src/name.js
export var name = "john";
export default "this is default"
ESM 和 CJS 的区别在于,ESM 给 exports 做了一层代理。

当我们使用 ESM 导入的值,实际上都是代理值,我们每次访问这个值其实都是通过要这个代理,这也就是为什么导出后改变这个值,导入的文件也能拿到更新后的值。
扩展
设置值类型
在 EsModule 的 __webpack_modules__
中有一个方法 __webpack_require__.r
这个是干什么用的呢?他其实就是把 exports标记成 Module
类型。

我们判断一个值的属性,通过 Object.prototype.toString.call()
方法,那么这个方法是根据什么判断出类型的?其实就是根据值的 Symbol.toStringTag
来识别。
举个例子:
js
const obj = {};
Object.defineProperty(obj, Symbol.toStringTag, {value: "Module"});
console.log(Object.prototype.toString.call(obj)); // [object Module]
CJS 和 ESM 区别
CJS | ESM | |
---|---|---|
语法类型 | 动态 | 静态 |
加载方式 | 运行时加载 | 编译时加载 |
加载行为 | 同步加载 | 异步加载 |
this指向 | 当前模块 | undefined |
能否修改 | 可以修改引用的值 | 引入的值是只读的 |
引用 | 基本类型复制 引用类型浅拷贝 | 动态只读引用 |
书写位置 | 任意位置 | 顶层 |
运行时加载和编译时加载
运行时加载会生成一个对象全部加载,运行时获得该对象。编译时加载直接从模块中加载,能做到按需加载,效率更高。