1.理解模块模式
将代码拆分成独立的块,然后再把这些块连接起来可以通过模块模式来实现。这种模式背后的思想:把逻辑分块,各自封装,相互独立,每个块自行决定对外暴露什么,同时自行决定引入执行哪些外部代码。
1.1模块标识符
完善的模块系统不会存在模块标识冲突的问题,模块标识符可以是字符串
, 文件的路径
或者文件名
等
1.2模块依赖
模块系统的核心是管理依赖。指定依赖的模块与周围的环境会达成一种契约。本地模块向模块系统声明一组外部模块(依赖),这些外部模块对于当前模块正常运行是必需的。模块系统检视这些依赖,进而保证这些外部模块能够被加载并在本地模块运行时初始化所有依赖。
1.3模块加载
加载模块的概念派生自依赖契约。当一个外部模块被指定为依赖时,本地模块期望在执行它时,依赖已准备好并已初始化。
1.4入口
相互依赖的模块,必须指定一个入口
。
模块加载是“阻塞的”,这意味着前置操作必须完成才能执行后续操作。
1.5异步依赖
可以让 JavaScript 通知模块系统在必要时加载新模块,并在模块加载完成后提供回调。这样做提升了性能,因为页面加载时只需要同步加载一个文件。
// 在模块 A 里面
load('moduleB').then(function(moduleB) {
moduleB.doStuff();
});
1.6动态依赖
有些模块系统要求开发者在模块开始列出所有依赖,而有些模块系统则允许开发者在程序结构中动态添加依赖。动态添加的依赖有别于模块开头列出的常规依赖,这些依赖必须在模块执行前加载完毕。
下面是动态依赖加载的例子:
if (loadCondition) {
require('./moduleA');
}
1.7静态分析
分析工具会检查代码结构并在不实际执行代码的情况下推断其行为。
1.8循环依赖
2.凑合的模块系统
ES6之前使用IIFE模拟模块
var Foo = (function() {
return {
bar: 'baz',
baz: function() {
console.log(this.bar);
}
};
})();
console.log(Foo.bar); // 'baz'
Foo.baz(); // 'baz'
类似地,还有一种模式叫作“泄露模块模式”(revealing module pattern)。这种模式只返回一个对象,其属性是私有数据和成员的引用:
var Foo = (function() {
var bar = 'baz';
var baz = function() {
console.log(bar);
};
return {
bar: bar,
baz: baz
};
})();
console.log(Foo.bar); // 'baz'
Foo.baz(); // 'baz'
嵌套模块
3.使用ES6之前的模块加载器
3.1 CommonJS
CommonJS 规范概述了同步声明依赖的模块定义。这个规范主要用于在服务器端实现模块化代码组织,但也可用于定义在浏览器中使用的模块依赖。CommonJS 模块语法不能在浏览器中直接运行。
require();
module.exports = {
}
在 CommonJS 中,模块加载是模块系统执行的同步操作
3.2异步模块定义
异步模块定义(AMD,Asynchronous Module Definition)的模块定义系统则以浏览器为目标执行环境,这需要考虑网络延迟的问题。AMD 的一般策略是让模块声明自己的依赖,而运行在浏览器中的模块系统会按需获取依赖,并在依赖加载完成后立即执行依赖它们的模块。
AMD 模块实现的核心是用函数包装模块定义。
// ID 为'moduleA'的模块定义。moduleA 依赖 moduleB,
// moduleB 会异步加载
define('moduleA', ['moduleB'], function(moduleB) {
return {
stuff: moduleB.doStuff();
};
});
AMD 也支持require
和exports 对象
,通过它们可以在 AMD 模块工厂函数内部定义 CommonJS风格的模块。
3.3通用模块定义
为了统一 CommonJS 和 AMD 生态系统,通用模块定义(UMD,Universal Module Definition)规范应运而生。UMD 可用于创建这两个系统都可以使用的模块代码。本质上,UMD 定义的模块会在启动时检测要使用哪个模块系统,然后进行适当配置,并把所有逻辑包装在一个立即调用的函数表达式(IIFE)中。虽然这种组合并不完美,但在很多场景下足以实现两个生态的共存。
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD。注册为匿名模块
define(['moduleB'], factory);
} else if (typeof module === 'object' && module.exports) {
// Node。不支持严格 CommonJS
// 但可以在 Node 这样支持 module.exports 的
// 类 CommonJS 环境下使用
module.exports = factory(require(' moduleB '));
} else {
// 浏览器全局上下文(root 是 window)
root.returnExports = factory(root. moduleB);
}
}(this, function (moduleB) {
// 以某种方式使用 moduleB
// 将返回值作为模块的导出
// 这个例子返回了一个对象
// 但是模块也可以返回函数作为导出值
return {};
}));
4.使用ES6模块
4.1模块标签及其定义
<script type="module">
// 模块代码
</script>
<script type="module" src="path/to/myModule.js"></script>
- 解析到<scripttype=“module”>标签后会立即下载模块文件,但执行会延迟到文档解析完成。
- <script type=“module”>在页面中出现的顺序就是它们执行的顺序。类似于<script defer>
4.2模块加载
完全支持 ECMAScript 6 模块的浏览器可以从顶级模块加载整个依赖图,且是异步完成的。浏览器会解析入口模块,确定依赖,并发送对依赖模块的请求。这些文件通过网络返回后,浏览器就会解析它们的内容,确定它们的依赖,如果这些二级依赖还没有加载,则会发送更多请求。这个异步递归加载过程会持续到整个应用程序的依赖图都解析完成。解析完依赖图,应用程序就可以正式加载模块了。
4.3模块行为
- 模块代码只能在加载后执行
- 模块只能加载一次
- 模块是单例
- 模块可以定义公共接口
- 模块可请求加载其他模块
- 支持循环依赖
- ES6模块系统默认在严格模式下执行
- ES6模块不共享全局命名空间
- 顶级模块中this为undefined
- 模块中的var声明不会加到window
- ES6模块异步加载和执行
4.4模块导出
命名导出、默认导出
4.5模块导入
import 语句被提升到模块顶部。因此,与 export 关键字类似,import 语句与使用导入值的语句的相对位置并不重要。不过,还是推荐把导入语句放在模块顶部。