1. 第8部分. 模块化
1.1. 什么是模块化
模块化是指在解决某一个复杂问题或者一系列的杂糅问题时,依照一种分类的思维把问题进行系统性的分解以之处理。
模块化是一种处理复杂系统分解为代码结构更合理,可维护性更高的可管理的模块的方式。可以想象一个巨大的系统代码,被整合优化分割成逻辑性很强的模块时,对于软件是一种何等意义的存在。
1.2. 为什么要模块化?
无模块时刻面临着:
全局变量的灾难
函数命名冲突
依赖关系不好管理
1.3. 模块化解决方案
1.3.1. 闭包
每个js文件都是IIFE包裹的,各个js文件分别在不同的词法作用域中,相互隔离,最后通过闭包的方式来暴露变量。每个闭包都是独立的文件,每个文件仍然通过script下载,标签顺序就是模块的依赖关系。
1.3.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()