第26章 模块
第26章 模块
理解模块模式
模块模式背后的思想 很简单:把逻辑分块,各自封装,相互独立,每个块自行决定对外暴露什么,同时自行决定引入执行哪 些外部代码。
模块标识符
模块标识符是所有模块系统通用的概念。模块系统本质上是键/值实体,其中每个模块都有个可用 于引用它的标识符。这个标识符在模拟模块的系统中可能是字符串,在原生实现的模块系统中可能是模 块文件的实际路径。每个模块都会与某个唯一的标识符关联,该标识符可用于检索模块。
模块依赖
模块系统的核心是管理依赖。指定依赖的模块与周围的环境会达成一种契约。本地模块向模块系统 声明一组外部模块(依赖),这些外部模块对于当前模块正常运行是必需的。模块系统检视这些依赖, 进而保证这些外部模块能够被加载并在本地模块运行时初始化所有依赖。
模块加载
加载模块的概念派生自依赖契约。当一个外部模块被指定为依赖时,本地模块期望在执行它时,依 赖已准备好并已初始化。加载模块涉及执行其中的代码,但必须是在所有依赖都加载 并执行之后。如果浏览器没有收到依赖模块的代码,则必须发送请求并等待网络返回。收到模块代码之 后,浏览器必须确定刚收到的模块是否也有依赖。然后递归地评估并加载所有依赖,直到所有依赖模块 都加载完成。只有整个依赖图都加载完成,才可以执行入口模块。
入口
相互依赖的模块必须指定一个模块作为入口(entry point),这也是代码执行的起点。因为 JavaScript 是顺序执行的,并且是单线程的,所以代码必须有执行的起点。模块加载是“阻塞的”,这意味着前置操作必须完成才能执行后续操作。
异步依赖
因为 JavaScript 可以异步执行,所以如果能按需加载就好了。换句话说,可以让 JavaScript 通知模块 系统在必要时加载新模块,并在模块加载完成后提供回调。
// 在模块 A 里面
load('moduleB').then(function(moduleB) {
moduleB.doStuff();
});
动态依赖
有些模块系统要求开发者在模块开始列出所有依赖,而有些模块系统则允许开发者在程序结构中动 态添加依赖。动态添加的依赖有别于模块开头列出的常规依赖,这些依赖必须在模块执行前加载完毕。
if (loadCondition) {
require('./moduleA');
}
动态依赖可以支持更复杂的依赖关系,但代价是增加了对模块进行静态分析的难度。
使用ES6之前的模块加载器
CommonJS
CommonJS 模块定义需要使用 require()指定依赖,而使用 exports 对象定义自己的公共 API。
var moduleB = require('./moduleB');
module.exports = {
stuff: moduleB.doStuff();
};
请求模块会加载相应模块,而把模块赋值给变量也非常常见,但赋值给变量不是必需的。调用 require()意味着模块会原封不动地加载进来。
无论一个模块在 require()中被引用多少次,模块永远是单例。模块第一次加载后会被缓存,后续加载会取得缓存的模块。在 CommonJS 中,模块加载是模块系统执行的同步操作。
。。。
AMD
。。。
使用ES6模块
ECMAScript 6 模块是作为一整块 JavaScript 代码而存在的。带有 type=”module”属性的script标签会告诉浏览器相关代码应该作为模块执行,而不是作为传统的脚本执行。模块可以嵌入在网页中, 也可以作为外部文件引入。
模块加载
ECMAScript 6 模块的独特之处在于,既可以通过浏览器原生加载,也可以与第三方加载器和构建工 具一起加载。
ES6 模块支持两种导出:命名导出和默认导出。