1. 无模块化
实现:文件分离,顺序导入
缺点:污染全局作用域,变量名冲突导致的报错
2. IIFE (Immediately-invoked Function Expression)
实现:利用函数块级作用域
const iifeModule = (function(){
let count = 1
function increase () {
return ++count
}
function reset () {
return count = 0
}
return {
increase,
reset
}
})()
iifeModule.increase()
iifeModule.reset()
优化IIFE,加入模块依赖
模块导入使用 iife 传参,模块导出使用 iife 的返回值
const iifeModule = (function(dependency1, dependency2){
let count = 1
function increase () {
return ++count
}
function reset () {
return count = 0
}
return {
increase,
reset
}
})(dependency1, dependency2)
iifeModule.increase()
iifeModule.reset()
Revealing Module Pattern
揭示模式模仿了 OOP 的思想,隐藏私有变量,只暴露公有变量和函数来操作私有变量
IIFE 优点:
- 从语法侧解决了全局变量作用域的问题,有了模块的雏形
IIFE 缺点:
- 多余的语法代码
- 除了解决作用域的问题,其他的复杂场景问题一概没有考虑
3. CJS - Commonjs
nodejs 的模块化方案
每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。
require方法用于加载模块
const dep1 = require('dep1Module')
const dep2 = require('dep2Module')
// use object dep1 do sth
...
exports.a = a
exports.b = b
// Or
module.exports = {
a, b
}
实现逻辑
使用
(function (require, module, exports) {
// use require
require(...)
// use exports
exports.a = a
// use module.exports
module.exports = {
a
}
})()
// require 模拟
function require(dep) {
// 定义闭包bm'ld
const module = {}
module.exports = {}
const code = file.readFileSync('dep')
// 函数执行完成之后,module.exports 将会被赋值
new Function('require', 'module', 'exports', code)(require, module, exports)
// 导出 dep 的 emodule.exports
return module.exports
}
-
优点:
CommonJS 率先在服务端实现了,从框架层面解决全局变量污染
、依赖
的问题 -
缺点:
主要是服务端的解决方案,不适用于客户端异步拉取依赖的场景
抛出新的问题 —— 异步依赖
4. AMD (Asynchronous Module Definition)
通过异步加载 + 允许定制回调
经典实现框架:require.js
定义方式
/**
* 通过 define 定义一个模块,然后通过 require 进行加载
* 最后一个参数是 工厂方法
**/
define(id, [...deps], callback)
require([...module], callback)
模块的定义
define('myModule', ['dep1', 'dep2'], (dep1, dep2) => {
// 业务逻辑
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
return {
increase, reset
}
})
模块的引入
require(['myModule'], myModule => {
myModule.increase()
})
- 优点:解决了浏览器异步加载依赖场景的问题,可以并行加载多个模块
- 缺点:不能按需加载
5. UMD (Universal Module Definition)
UMD 顶部一般有这样一段代码来兼容 CJS 和 AMD
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery', factory])
} else if (typeof 'exports' === 'object') {
// CJS
module.exports = factory(require('jquery'))
} else {
// 将模块绑定在全局变量上
// 这里的 root.jQuery 也是之前已经绑定了的 module
root.myModule = factory(root.jQuery)
}
})(this, function ($) {
// myModule 业务代码
return {
a,
b
}
})
- 优点:兼容 AMD 和 CJS,可以在双端运行
- 缺点:未解决 AMD 无法按需加载的问题
6. CMD (Common Module Definition)
主要应用框架 sea.js
require & require.async
- require 导入同步模块
- require.async 导入异步模块,第二个参数是 callback
define('myModule', (require, exports, module) => {
let $ = require('jquery')
// jquery 相关逻辑
...
let dep1 = require.async(['dep1'], dep1 => {
// dep1 module 相关业务逻辑
...
})
})
- 优点:按需加载,同时支持同步和异步 require
- 缺点:依赖打包,加载逻辑存在于每个模块中,模块体积扩大
ESM
使用
import 导入模块
export 导出模块,export default 导出默认模块
import dep1 from 'dep1'
// 业务逻辑
let count = 0
export const increase = () => ++count
export const reset = () => count = 0
export default {
increase, reset
}
模板引入
<script type="module" src="myModule.js"></script>
node 端引入,mjs
import { increase, reset } from './myModule.mjs'
increase()
reset()
动态导入
ES11 原生解决方案
import('dep').then(dep => {
...
})
- 优点:官方提出的统一形态的模块化,解决了上面遇到的各种问题
(模块作用域、依赖导入导出、异步导入等) - 缺点:本质上还是运行时的依赖分析
解决模块化的新思路 —— 前端工程化
背景
根本问题 —— 运行时的依赖分析
方案:编译时分析依赖,同时优化项目
grunt gulp webpack vite