笔记 - JS模块化
历史背景
JS本身简单的页面设计:页面动画 + 表单提交
并无模块化 or 命名空间概念
随着前端页面日益复杂,之前的方式不能满足需求
所以,JS的模块化需求日益增加
发展历程
幼年期:无模块化
- 页面需要不同功能的 js - 动画、表单、格式化
- 多种js文件被分在不同的文件中
- 不同的文件由被同一个模板引用
<script src="jquery.js"></script>
<script src="main.js"></script>
<script src="dep1.js"></script>
优势:文件分离可以起到模块复用和较好维护的作用。
劣势:会污染全局作用域 => 不利于大型项目的开发和多人团队的协作。
成长型:模块化的雏形 - IIFE (语法侧的优化)
主要是增加了对作用域的把控
例子:
// 普通做法 - 污染全局作用域
let count = 0;
const increase = () => ++count;
const reset = () => { count = 0; }
increase();
reset();
// 利用 IIFE
const iifeModule = (() => {
let count = 0;
return {
increase: () => ++count,
reset: () => {
count = 0
}
}
})();
iifeModule.increase();
iifeModule.reset();
// 对上面 IIFE 进行优化 - 注入其他依赖
const iifeModule = ((dependencyModule1, dependencyModule2) => {
let count = 0;
return {
increase: () => ++count,
reset: () => {
count = 0
}
}
})(dependencyModule1, dependencyModule2);
iifeModule.increase();
iifeModule.reset();
// 模仿早期jquery的依赖处理以及模块加载
const iifeModule = ((dependencyModule1, dependencyModule2) => {
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
// 其实是使用了揭示模式
return {
increase, reset
}
})(dependencyModule1, dependencyModule2);
iifeModule.increase();
iifeModule.reset();
成熟期:CJS - CommonJs
特征:
- 通过 module + exports 去对外暴露接口
- 通过 require 来调用其他模块
// main.js 文件
const dependencyModule1 = require('./dependencyModule1');
const dependencyModule2 = require('./dependencyModule2');
// 处理方式
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
// 做一些跟引入依赖相关事宜...
// 暴露接口
// 1
exports.increase = increase;
exports.reset = reset;
// 2
module.exports = {
increase,reset
}
const { increase, reset } = require('./main.js');
increase();
reset();
(function(thisValue, exports, require, module) {
const dependencyModule1 = require('./dependencyModule1');
const dependencyModule2 = require('./dependencyModule2');
// 业务逻辑...
}).call(thisValue, exports, require, module)
- 优点:CommonJs率先在服务端实现了,从框架层面解决了依赖、全局变量污染的问题
- 缺点:主要针对了服务端的解决方案,对异步拉取依赖的处理整合不那么友好
AMD规范
新增定义方式:
// 通过define来定义一个模块,然后require进行加载
/*
* define
* params: 模块名,依赖模块,工厂方法
**/
define(id, [depends], callback);
require([module], callback);
模块定义方式:
define('amdModule', [dependencyModule1, dependencyModule2], (dependencyModule1,
dependencyModule2) => {
// 业务逻辑
// 处理部分
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
return {
increase, reset
}
})
引入模块:
require(['amdModule'], amdModule => {
amdModule.increase();
})
define('amdModule', [], require => {
// 引入部分
const dependencyModule1 = require(./dependencyModule1);
const dependencyModule2 = require(./dependencyModule2);
// 处理部分
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
// 做一些跟引入依赖相关事宜……
return {
increase, reset
}
})
define('amdModule', [], (require, export, module) => {
// 引入部分
const dependencyModule1 = require(./dependencyModule1);
const dependencyModule2 = require(./dependencyModule2);
// 处理部分
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
// 做一些跟引入依赖相关事宜……
export.increase = increase();
export.reset = reset();
})
define('amdModule', [], require => {
const otherModule = require('amdModule');
otherModule.increase();
otherModule.reset();
})
(define('amdModule', [], (require, export, module) => {
// 引入部分
const dependencyModule1 = require(./dependencyModule1);
const dependencyModule2 = require(./dependencyModule2);
// 处理部分
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
// 做一些跟引入依赖相关事宜……
export.increase = increase();
export.reset = reset();
}))(
// 目标是一次性区分CommonJSorAMD
typeof module === "object"
&& module.exports
&& typeof define !== "function"
? // 是 CJS
factory => module.exports = factory(require, exports, module)
: // 是AMD
define
)
- 优点:适合在浏览器中加载异步模块,可以并行加载多个模块
- 缺点:会有引入成本,不能按需加载
CMD规范
define('module', (require, exports, module) => {
let $ = require('jquery');
// jquery相关逻辑
let dependencyModule1 = require('./dependecyModule1');
// dependencyModule1相关逻辑
})
- 优点:按需加载,依赖就近
- 缺点:依赖于打包,加载逻辑存在于每个模块中,扩大模块体积
ES6模块化
// 引入区域
import dependencyModule1 from './dependencyModule1.js'
import dependencyModule2 from './dependencyModule2.js'
// 实现代码逻辑
let count = 0;
export const increase = () => ++count;
export const reset = () => {
count = 0;
}
// 导出区域
export default {
increase,reset
}
// 模块引入
<script type="module" src="esModule.js"></script>
ES11原生解决方案:
import('./esModule.js').then(dynamicEsModule => {
dynamicEsModule.increase();
})
- 优点(重要性):通过一种最统一的形态整合了js的模块
- 缺点(局限性):本质上还是运行时的依赖分析
(重要)解决模块化的新思路 - 前端工程化
背景
<!doctype html>
<script src="main.js"></script>
<script>
// 给构建工具一个标识位
require.config(__FRAME_CONFIG__);
</script>
<script>
require(['a', 'e'], () => {
// 业务处理
})
</script>
</html>
define('a', () => {
let b = require('b');
let c = require('c');
export.run = () {
// run
}
})
工程化实现
{
a: ['b', 'c'],
b: ['d'],
e: []
}
<!doctype html>
<script src="main.js"></script>
<script>
// 构建工具生成数据
require.config({
"deps": {
a: ['b', 'c'],
b: ['d'],
e: []
}
})
</script>
<script>
require(['a', 'e'], () => {
// 业务处理
})
</script>
</html>
define('a', ['b', 'c'], () => {
// 执行代码
export.run = () => {}
})
- 优点:
- 构建时生成配置,运行时执行
- 最终转化成执行处理依赖
- 可以拓展