# 1. 第8部分. 模块化
## 1.1. 什么是模块化
模块化是指在解决某一个复杂问题或者一系列的杂糅问题时,依照一种分类的思维把问题进行系统性的分解以之处理。
模块化是一种处理复杂系统分解为代码结构更合理,可维护性更高的可管理的模块的方式。可以想象一个巨大的系统代码,被整合优化分割成逻辑性很强的模块时,对于软件是一种何等意义的存在。
## 1.2. 为什么要模块化?
无模块时刻面临着:
- 全局变量的灾难
- 函数命名冲突
- 依赖关系不好管理
## 1.3. 模块化解决方案
### 1.3.1. 闭包
每个js文件都是IIFE包裹的,各个js文件分别在不同的词法作用域中,相互隔离,最后通过闭包的方式来暴露变量。每个闭包都是独立的文件,每个文件仍然通过script下载,标签顺序就是模块的依赖关系。
### 1.3.2. 面向对象
在闭包的基础上,所有返回值都是对象,对象其实就是一些方法和属性的集合
优点:
1. 规范化输出,更加统一的便于相互依赖和引用
2. 使用类的方式开发,辩护后面的依赖进行扩展
### 1.3.3. UI
雅虎出品的一个工具,包含模块化管理、js压缩、混淆、请求合并等性能优化的工具。
通过YUI全局对象去管理不同模块,所有模块都只是对象上的不同属性,相当于是不同程序运行在操作系统上。核心是闭包。
------
### 1.3.4. CommonJS
commonjs作为Node模块化规范
特点:
- 每个文件都是一个Module实例,原生Module对象
- 文件内通过require对象引入指定模块
- 所有文件加载均是同步完成
- 通过module关键字暴露内容:
> exports与module.exports 为了方便Node为每个模块提供一个exports变量,指向module.exports, 等同于var exports = module.exprots。所以exports不能直接指向一个变量。
- 每个模块加载一次后就会被缓存
- 模块编译本质上是沙箱编译
- node的API,只能在服务端上运行
优点:
- 强大的查找模块功能,开发十分方便
- 标准化的输入输出,非常统一
- 每个文件引入自己的依赖,最终形成文件依赖树
- 模块缓存机制,提高编译效率
- 利用node实现文件同步读取
- 依赖注入变量的 **沙箱** 编译实现模块化
```
var a = require('');
module.exports = {}
```
沙箱编译之后
```
(function(exports, require, module,__filename,__dirname){})();
```
### 1.3.5. AMD规范和RequireJS(Asynchronous Module Definition)异步模块
AMD规范:采用异步方式加载模块,模块加载不影响它后面的语句进行,所有依赖这个模块的语句,都定义在一个回调函数中,等到所有依赖加载完成后,这个回调函数才会运行。
RequireJs 是模块化工具框架,是AMD规范的具体实现
特点
- 配置文件:有一个main文件,配置不同模块的路径,以及shim不满足AMD规范的js文件
- 依赖前置,动态创建script引入依赖,在script标签的onload事件监听文件加载完毕,一个模块的回调函数必须等所有依赖都加载完毕之后,才可以执行,类似Promise.all
加载
```
require([module], callback)
```
定义
```
define(id?, dependencies?, factory);
```
```
// main.js
requirejs.config({
shim:{},
paths:{
a:'/a.js',
b:'/b.js',
c:'/c.js',
index:'/index.js',
}
})
require(['index'], function(index){
index();
})
```
优点
- 动态并行加载js。依赖前置,无需再考虑js加载顺序问题
- 核心还是注入变量的沙箱编译,解决模块化问题
- 规范化输入输出
- 对于不满足AMD规范的文件可以很好的兼容
### 1.3.6. CMD规范和SaeJs
**特点**
- define定义模块,require加载模块,exporta暴露变量
- 不同于AMD的依赖前置,CMD按需加载
- 推崇api功能单一,一个模块干一件事
**核心特点**
- 需要配置模块对应的url
- 入口文件执行只有,根据文件内的依赖关系整理出依赖树,然后通过script标签加载依赖
- 依赖加载完毕之后,执行根factory
- 在factory中遇到require,则去执行对应模块的factory,实现就近依赖
- 类似commonJS,对所有模块进行缓存模块url就是id
- 类似commonJs可以使用相对路径加载模块
- exports和return都可以暴露变量
### 1.3.7. ES6 Module
核心思想是尽量静态化,使得编译时就能确定依赖关系,以及输入和输出的变量。
**es6和commonJS的区别**
- ES6中的模块化在CommonJS的基础上,增加了关键字import from export as default
- commonjs模块输出的是一个值的拷贝,es6模块输出的是值的引用
- commonjs模块是运行时加载,es6模块是编译时输出接口
- comminjs 模块输出的是值的拷贝,一旦输出一个值,模块内部的变化就影响不带这个值
- es6模块运行机制是js引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用,等到脚本真正执行的时候,再根据这个只读引用,到被加载的那个模块里面去取值。
**关键字**
- **import** 命令用于输入其他模块提供的功能;具有提升功能,本质是import命令是编译阶段执行的,在代码运行之前;静态执行,不能有变量,因为变量是运行时执行的;重复多次只加载一次
- **export** 用于规定模块的对外接口,可以输出变量、函数、类;规定的是对外接口,必须与模块内部的变量一一对应,不能只输出一个值;是动态绑定关系,通过该接口可以取到模块内部实时的值。可以出现在顶层任意位置,不能被包含
- **as** 关键字重命名
- **from** 指定路径,可以是相对,也可以是绝对,也可以是模块名。模块名不带路径,必须通过配置
- **default**
```
function foo(){}
export default foo;
```
```
import customName from ''
```
由于使用import命令的时候,需要知道所加载的变量名或函数名,为了给用户提供方便,可以不阅读文档加载模块。利用export default为模块指定默认输出。
利用default输出的接口都变为匿名接口,引入的时候可以直接重命名为任意名称使用;
只能定义一次,import不需要大括号。
本质上就是输出一个叫做default的变量或方法,然后系统允许你添加任意名字;
只是输出一个名为default的变量或方法,后面不能跟变量声明语句,可以跟class
**export 和 import 混合使用**
export {foo, bar} from 'my_module' 并对外转发提供接口
**import()**
动态加载
执行到这一句的时候,同步加载模块信息
没有建立静态连接关系,类似于node的require方法
import()模块加载成功之后,这个模块会作为一个对象,当作then方法的参数。因此可以使用对象解构赋值的语法,获取输出接口。模块有default输出接口,可以用参数直接获得
import()也可以用在async函数中
场景
- 按需加载
- 条件加载
- 动态模块路径
**引入**
script加入 type = 'module'
相当于script加上了defer,渲染完成之后加载,按照顺序加载
加上async之后,就不按照顺序执行了,按照加载完成就执行该模块
**模块转码** **es6 module transpiler** 转码器,将es6模块转为commonjs模块或者AMD模块
npm install -g es6-module-transpiler
compile-modules convert file.js file1.js
compile-modules convert -o out.js file1.js
**systemjs** 垫片库
在浏览器内加载es6模块、amd模块、commonjs模块,将其转为es5
System.import('./app.js').then()