前端模块化发展历程
本文将从以下三点来做一个详细讲解:
- 模块化产生
- 早期模块化解决方案
- 模块化规范的产生和差异
模块化产生
在早期的前端开发中,并没有模块的概念,模块只在服务端存在,用于处理复杂的业务通信等。 直到 AJAX 被提出,前端能够像后端请求数据,前端逻辑越来越复杂,就出现了许多问题:全局变量,函数名冲突,依赖关系不好处理... 随着业务逻辑的增加,对模块需求越来越大,所以才有了后续一系列 AMD、commonJS、ES6Module 规范。
早期模块化解决方案
两种解决方法:
- 匿名函数自调用(闭包): 形成私有变量; 栈内存处理
- 基于对象进行分组 堆内存处理; “单例模式思想” : 基于单独的实例, 来实现信息分组, 避免全局变量的污染
下面一个简单的例子
// 新闻板块 let newMOdel = (function(){ let time = new Date() const query = function query() {} const handle = function handel() {} return { query, handle } }()) // 皮肤板块 let skinModel = (function() { let time = '2021-07-05' const hanle = function handel(){ } newMOdel.query() }())
最早期的模块编程思想就是这种 “高级单例设计模式”「闭包+对象」的组合 模块块化编程思想:就是各个板块/模块 /功能 拼接成一起的东西 ,提出公共的模块。
带来的好处?
公用&复用性、提供开发效率、方便管理、团队协作开发 ; 问题:需要自己构建、根据模块间的依赖关系,需要明确导出顺序。 所以就产生了一些其他的模块化规范。
模块化规范 - AMD
AMD 即 Asynchronous Module Definition:异步模块加载,代表 require.js
RequireJS是一个遵守AMD规范的工具库,用于客户端的模块管理。它通过 define 方法,将代码定义为模块;通过 require 方法,实现代码的模块加载,使用时需要下载和导入项
文件目录
├── AMD
├── moduleA.js
├── moduleB.js
├── main.js
└── require.min.js
简单实现一个require.js
let factories = {} function define(moduleName,factory) { factories[moduleName] = factory } function require(modules,callback) { modules = modules.map(function(item){ let factory = factories[item]; // 定义好每一个 然后把它执行 return factory() // 执行之后返回的东西 放到modules }); callback(...modules) // 然后回掉函数执行这些modules } /**使用AMD */ define('moduleA', function() { return { fn() { console.log('moduleA') } } }); define('moduleB', function() { return { fn() { console.log('moduleB') } } }); require(['moduleB','moduleA'],function(moduleB,moduleA) { moduleB.fn() moduleA.fn() })
模块化规范 - CMD
CMD即 Common Module Definition : 通用模块加载。
CMD(Sea.js )& CommonJs规范(Node.js)。
问题:CommonJs只能在 node 环境下支持,客户端/浏览器不支持 CommonJS 规范
那如何让浏览器支持CommonJs规范? 所以有了 Sea.js ,也就产生了CMD规范(Sea.js 就是Commonjs规范直接搬到浏览器上 )
AMD、CMD 区别
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出 CMD是SeaJS在推广过程中对模块化定义的规范化产出
区别:
- 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
- CMD 推崇依赖就近,AMD 推崇依赖前置。
模块化规范 - ES6Module
ModuleA.js
const sum = function sum(...args){ let len = args.length let firstItem = args[0] if(len === 0) return 0; if(len === 1) return firstItem; return args.reduce((total,item) => { return total + item }) } export default { sum }
moduleB.js
import A from './a.js' const average = function average(...args) { let len = args.length let firstItem = args[0] if(len === 0) return 0; if(len === 1) return firstItem; return (A.sum(...args) / args.length).toFixed(2) } export default { average }
main.js
import A from './a.js' import B from './b.js' console.log(A.sum(1,2,3)) console.log(B.average(1,2,3))
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script type="module" src="./main.js"></script> </body> </html>
注意:
- es6Module 不需要引入外部依赖,浏览器可以直接运行,但要告诉浏览器我是esModule规范,type='module' <script type="module" src="./main.js"></script>
- es6Module 浏览器不支持file协议,要在本地起一个服务,可以在vscode上装一个live serve插件,给本地一个服务。
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
特点:webpack 支持、浏览器也支持.
CommonJS、ES6Module差异
- CommonJs输出的是一个值的拷贝,ES6输出的是值的引用
- CommonJs是运行时加载,ES Module是编译时就确认了依赖关系
第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。