模块化
一、模块化的理解
1.什么是模块化?
-
将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起
-
块的内部数据/实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信
2.一个模块的组成
-
数据—>内部的属性
-
操作数据的行为—>内部的函数
3.模块化
- 编码时是按照模块一个一个编码的, 整个项目就是一个模块化的项目
二、JS模块发展背景
1. 幼年期(也就是无模块化)
- 开始需要在页面中增加一些不同的js:动画、表单、格式化
- 多种js文件被分在不同的文件中
- 不同的文件又被同一个模板引用(文件分离是最基础的模块化第一步)
<script src="jquery.js"></script>
<script src="main.js"></script>
<script src="dep1.js"></script>
//…
- 出现的问题:污染全局作用域 => 不利于大型项目的开发以及多人团队的共建
2. 成长期(模块化的雏形 - IIFE(语法侧的优化) )
IIFE( 立即调用函数表达式)是一个在定义时就会立即执行的JavaScript函数
- 作用域的把控
// 定义一个全局变量
let count = 0;
// 代码块1
const increase = () => ++count;
// 代码块2
const reset = () => {
count = 0;
}
increase();
reset();
- 利用函数块级作用域
(()=>{
let count = 0;
//...
})
- 仅定义了一个函数 如果立即执行,就在函数后加(),表示立即执行
(() => {
let count = 0;
// ……
}();
-
以上步骤,仅是初步实现了一个最最简单的模块.
下面,尝试去定义一个最简单的模块
const iifeModule = (() =>{
let count = 0;
return {
increase: () => ++count;
reset: () => {
count = 0;
}
}
})();
iifeModule.increase();
iifeModule.reset();
- 相关问题
1)有额外依赖时,如何优化IIFE相关代码?
答:依赖其他模块的IIFE
const iifeModule = ((dependencyModule1, dependencyModule2) => { //dependencyModule即为其他模块
let count = 0;
return {
increase: () => ++count;
reset: () => {
count = 0;
}
}
})(dependencyModule1, dependencyModule2);
iifeModule.increase();
iifeModule.reset();
2)了解早期jquery的依赖处理以及模块加载方案吗?/ 了解传统IIFE是如何解决多方依赖的问题
答:IIFE加传参调配,实际上JQuery框架其实应用了revealing(揭露)的写法
const iifeModule = ((dependencyModule1, dependencyModule2) => {
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
return {
increase, reset
}
})(dependencyModule1, dependencyModule2);
iifeModule.increase();
iifeModule.reset();
3. 成熟期( Commonjs(CJS) )
- 由node.js制定
特征:1.通过 module + exports 去对外暴露接口
2.通过 require 来调用其他模块
- 模块组织方式:
1).main.js文件
// 引入部分
const dependencyModule1 = require(./dependencyModule1);
const dependencyModule2 = require(./dependencyModule2);
// 处理部分
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
// 做一些跟引入依赖相关事宜……
// 暴露接口部分
exports.increase = increase;
exports.reset = reset;
module.exports = {
increase, reset
}
2).模块使用方式( require 调用即可 )
const { increase, reset } = require('./main.js');
increase();
reset();
4. Commenjs优缺点
- 优点:CommonJS率先在服务端实现了,从框架层面解决依赖、全局变量污染的问题
- 缺点:主要针对了服务端的解决方案。对于异步拉取依赖的处理整合不是那么的友好
那么在此产生了新的问题 ----- 异步依赖 ,就需要AMD来解决
三、AMD规范
方法:通过异步加载 和 允许制定回调函数
经典实现框架: require.js
1. 新增定义方式
- 通过 define 来定义一个模块,然后 require 进行加载
//params: 模块名,依赖模块,工厂方法
define(id, [depends], callback);
require([module], callback);
2. 模块定义方式
define('amdModule', ['dependencyModule1', 'dependencyModule2'], (dependencyModule1, dependencyModule2) => {
// 业务逻辑
// 处理部分
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
return {
increase, reset
}
})
3. 引入模块
require(['amdModule'], amdModule => {
amdModule.increase();
})
4.相关问题
4.1 如果在AMDmodule中想兼容已有代码,如何做?
define('amdModule', [], require => {
// 引入部分
const dependencyModule1 = require(./dependencyModule1);
const dependencyModule2 = require(./dependencyModule2);
// 处理部分
let count = 0;
const increase = () => ++count;
const reset = () => {
count = 0;
}
// 做一些跟引入依赖相关事宜……
return {
increase, reset
}
})
4.2 AMD中使用revealing
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();
})
4.3 兼容AMD&CJS / 如何判断CJS和AMD UMD的出现
(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
)
5. AMD优缺点
- 优点:适合在浏览器中加载异步模块,可以并行加载多个模块
- 缺点:会有引入成本,不能按需加载
四、CMD规范
- 按需加载
主要应用框架:sea.js
define('module', (require, exports, module) => {
let $ = require('jquery');
// jquery相关逻辑
let dependencyModule1 = require('./dependecyModule1');
// dependencyModule1相关逻辑
})
CMD优缺点
- 优点:按需加载,依赖就近
- 缺点:依赖于打包,加载逻辑存在于每个模块中,扩大模块体积
相关问题: AMD & CMD 的区别
答:依赖就近,按需加载
五、ES6模块化
1. 新增定义
引入关键字 ---- import
导出关键字 ---- export
2. 模块引入、导出和定义的地方
// 引入区域
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
}
3. 模板引入的地方
<script type="module" src="esModule.js"></script>
4. 在node中
import { increase, reset } from './esModule.mjs';
increase();
reset();
import esModule from './esModule.mjs';
esModule.increase();
esModule.reset();
5. 相关问题
ES6中的动态模块是哪些?
答: export 和 promise
6. ES11原生解决方案(包含了 promise)
import('./esModule.js').then(dynamicEsModule => {
dynamicEsModule.increase();
})
7. ES6模块化优缺点
- 优点(重要性):通过一种最统一的形态整合了js的模块化
- 缺点(局限性):本质上还是运行时的依赖分析
六、解决模块化的新思路 - 前端工程化
1. 背景
根本问题 - 前端的模块化处理方案依赖于运行时分析
2.问题解决方案
线下执行 grunt、gulp、webpack…
<!doctype html>
<script src="main.js"></script>
<script>
// 给构建工具一个标识位
require.config(__FRAME_CONFIG__)
</script>
<script>
require(['a', 'e'], () => {
// 业务处理
})
</script>
</html>
3. 工程化实现(思路)
1.扫描依赖关系表
{//a b e的全局依赖
a: ['b', 'c'],
b: ['d'],
e: []
}
2.重新生成依赖数据模板
<!doctype html>
<script src="main.js"></script>
<script>
// 构建工具生成数据
require.config({
"deps": {
a: ['b', 'c'],
b: ['d'],
e: []
}
})
</script>
<script>
require(['a', 'e'], () => {
// 业务处理
})
</script>
</html>
3.执行工具,采用模块化方案解决模块化处理依赖
define('a', ['b', 'c'], () => {
// 执行代码
export.run = () => {}
})
4. 工程化实现优点
- 构建时生成配置,运行时执行
- 最终转化成执行处理依赖
- 可以拓展