# 前端模块化
##### 前言
随着Ajax技术的兴起,前后端分离的开发模式逐渐占领了几乎整个市场,现在的服务器带宽也足够完成大量数据交互,于是很多以前在服务器端的逻辑也可以移植到前端来处理了,从而减轻服务器的压力,当然,这样的话Javascript代码就会越来越复杂。而Javascript作为一门松散的弱类型语言,在一个大型项目面前如果没有合理的拆分与规划,将会变得很难维护,这个时候我们就需要把前端逻辑做一个块化的处理。
**所谓模块化就是把需要重复使用的功能封装成模块,一个页面有一个统筹全局的对象把所有模块引入进来,最终形成一个产品,做成一个完整的项目。**
JS本身在ES6以前没有模块化的,ES6有模块化了,以前主流浏览器对于ES6模块化的支持度不足,所以一般不能直接使用,所以使用第三方的模块化实现。
## 为什么要用 前端模块化
> 早期,js代码量小, 所有Javascript代码可以都写在一个文件里面,只要加载一个js文件就够了。
>
> 但是后来,随着功能需求变多, 所有功能写在一个文件不好维护, 就拆分成了多个文件, 需要依次加载多个文件。如下:
```html
<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
<script src="4.js"></script>
<script src="5.js"></script>
<script src="6.js"></script>
这段代码依次加载多个js文件。
这样的写法有很大的缺点:
-
加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长
-
由于js文件之间存在依赖关系,因此必须严格保证加载顺序(比如上例的1.js要在2.js的前面),依赖性最大的模块一定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。
为了解决这两个问题 :
- 解决命名冲突
- 便于依赖管理
最早的时候使用自执行函数(IIFE)来实现模块化,它是一个伪模块化标准
模块化的好处
就好比要生产一台挖掘机,是由各个厂商提供的配件组装出来的,而不是由一家公司从头到尾生产,这样的好处是各个零部件各司其职,如果有一个功能坏掉了只需要找到对应的零部件解决问题即可,从而避免将来维修的麻烦。
作为代码也是同样的道理,一个完整的项目可以由很多个模块组成,如果某部分功能需要修改或者升级只需要找到对应模块的代码进行维护即可,大大提高了代码的可读性以及节约了维护成本。
模块化好处:
- 防止命名冲突
- 代码复用
- 高维护性
模块化规范
模块的使用一般分为导入和导出,定义一个模块需要导出出去在需要使用的地方导入。所谓模块化规范就是规定了模块的使用方式,不同的规范制定了不同的导入和导出的方式。常见的模块化规范有如下几种:
AMD
Asynchronous Module Definition 异步模块定义
出现原因:为了解决页面堵塞,往往会采用异步加载js的方式,但这种方式会带来一 些不确定因素;为此,James Burke(詹姆斯·布克) 便搞了一个AMD(Asynchronous Module Definition 异步模块定义)规范异步加载模块,模块加载不影响后续语句执行。
特点:
依赖前置:提前引入,文件开头把需要的模块一次性全部引入,后面直接使用
前期消耗比较大,后期执行效率很高
作用:
实现了js异步加载,避免页面加载出现假死
管理模块之间的依赖性,便于代码的编写与维护
简化了过于复杂的模块依赖关系,可以更好的管理模块
代表作是: require.js
CMD
Common Module Definition 通用模块定义
CMD 即Common Module Definition通用模块定义,CMD规范是国内发展出来的,就像AMD有个requireJS,CMD代表作是SeaJS,SeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)上有所不同
特点:
Sea.js 推崇一个模块一个文件,遵循统一的写法
按需加载:在代码执行过程当中需要一个模块了才去加载
整个曲线比较平缓
代表作是sea.js,但是现在已经很少使用了
区别:CMD 和 AMD 的差异体现在对模块的态度,AMD 倾向于加载完所有的模块后再执行当前模块,必须在定义模块的时候就要声明其依赖的模块(推崇依赖前置)。而CMD推崇就近依赖,只有在用到某个模块的时候再去调用这个模块
ES6 Module
现在大多数主流浏览器都支持了
另外:服务器端Node.js遵循commonJS规范,module.exports 导出模块,require引入模块。
require.js
我们今天以require.js为例来说说前端模块化的使用。
一、初探
现在我们在页面中需要写两个script
标签,一个用来引入require.js
,另外一个用来引入当前页面所需要的js,这样还是比较麻烦,require.js
给我们提供了更简单的方式,我们可以在引入require.js
的script
中写一个data-main
属性,如下
<script src="./js/require.min.js" data-main="./js/index"></script>
这样就可以少写一个script
标签,因为require.js
会帮助我们找到当前script
标签上的自定义属性data-main
并且根据属性值找到文件运行。
二、基本使用
比如我们定义一个a
模块,a.js
的代码可以如下:
// module a
define(() => {
class A {
constructor (name) {
this.name = name
}
say () {
console.log(this.name)
}
}
return A
})
再定义一个b
模块,b.js
代码如下:
// module b
define(() => {
class B {
constructor () {
this.name = 'module b'
}
say () {
console.log(this.name)
}
}
return new B()
})
这里定义了一个类并分别return
了这个类或者它的实例,这样我们在需要的时候就可以在需要使用的页面,比如与之同级的index.js
中使用require
函数来引入了:
require(['./a', './b'], (ModuleA, mB) => {
console.log('index page code')
new ModuleA('a').say()
mB.say()
})
我们通过require
可以依次引入我们所需要的模块,并且在回调函数里按顺序接收各自模块的返回值来使用,这样就使得代码可以非常方便的完成服用,实现模块化了。
三、配置模块公共路径
现在模块化已经具有一定规模了,但是在正式项目中文件路径通常是比较复杂的,所以每次要依赖某个模块都写路径的方式比较麻烦,因此我们可以将所有模块做一个简单的配置,我们可以新建一个config.js
注意点:配置baseUrl属性,baseUrl以值以引用require.js的HTML为基准
配置
require.config({
baseUrl: '/', // 当前项目跟路径,可能需要视具体情况稍做调整
paths: {
// 模块名: '路径' // 注意:路径一定不能有后缀
a: 'js/a',
b: 'js/b'
}
})
使用
require(['./config'], () => {
require(['a', 'b'], (ModuleA, mB) => {
console.log('index page code')
new ModuleA('a').say()
mB.say()
})
})
这样做的好处是现在引入模块不用写路径了,而是直接使用在config.js
里配置好的模块名即可,简单高效
ES模块化(Module)
介绍
- 2015 年发布,ES6语法里面自带了个模块化标准,各大浏览器厂商并不买账
- 2016年开始,Vue出现了,人家出现了一个脚手架(开发的大框架直接给你搭建好)
- 搭建这个架子的时候,内置了ES6模块化标准
- 2018年,各大浏览器厂商开始原生支持ES6模块化标准
- 2018年中,Chrome 丰先原生支持ES6模块化
- 语法: 变成了Js的语法,和关键字,不需要任何第三方文件的引入
特点:
- 页面必须在服务器上打开 可以使用 live server 插件
- 如果你想使用模块化语法,script 标签要加一 个属性type=”module"
- 依赖前置
使用:
1.每一个文件都可以作为独立模块,页面都可以作为整合文件
2.导出语法
export var num = 200
export default {
导出的内容
}
3.导入语法
接收export 导出
import {接收变量} from '哪个J5文件'
接收 export default 导出
import 变量 from '哪一个JS文件”
ES2020 发布新的标准,多了一个按需加载的模块化
语法: var 变量= import(你要加载的文件)
import(你要加载的文件).then(function(){})