【红宝书笔记精简版】第二十六章 模块

目录

26.3 使用 ES6 之前的模块加载器

26.3.1 CommonJS

26.3.2 异步模块定义

26.3.3 通用模块定义UMD

26.4 使用 ES6 模块

26.4.1 模块标签及定义

26.4.2 模块加载

26.4.3 模块行为

26.4.4 模块导出

26.4.5 模块导入

26.5 小结


26.3 使用 ES6 之前的模块加载器

26.3.1 CommonJS

CommonJS 模块定义需要使用 require()指定依赖,而使用 exports 对象定义自己的公共 API。 下面的代码展示了简单的模块定义:

var moduleB = require('./moduleB'); 
module.exports = { 
 stuff: moduleB.doStuff(); 
}; 

无论一个模块在 require()中被引用多少次,模块永远是单例。在下面的例子中,moduleA 只会 被打印一次。这是因为无论请求多少次,moduleA 只会被加载一次。

CommonJS 依赖几个全局属性如 require 和 module.exports。如果想在浏览器中使用 CommonJS 模块,就需要与其非原生的模块语法之间构筑“桥梁”。模块级代码与浏览器运行时之间也需要某种“屏 障”,因为没有封装的 CommonJS 代码在浏览器中执行会创建全局变量。这显然与模块模式的初衷相悖。 常见的解决方案是提前把模块文件打包好,把全局属性转换为原生 JavaScript 结构,将模块代码封 装在函数闭包中,最终只提供一个文件。为了以正确的顺序打包模块,需要事先生成全面的依赖图。

26.3.2 异步模块定义

CommonJS 以服务器端为目标环境,能够一次性把所有模块都加载到内存,而异步模块定义(AMD, Asynchronous Module Definition)的模块定义系统则以浏览器为目标执行环境,这需要考虑网络延迟的 问题。AMD 的一般策略是让模块声明自己的依赖,而运行在浏览器中的模块系统会按需获取依赖,并 在依赖加载完成后立即执行依赖它们的模块。 AMD 模块实现的核心是用函数包装模块定义。这样可以防止声明全局变量,并允许加载器库控制 何时加载模块。包装函数也便于模块代码的移植,因为包装函数内部的所有模块代码使用的都是原生 JavaScript 结构。包装模块的函数是全局 define 的参数,它是由 AMD 加载器库的实现定义的。 AMD 模块可以使用字符串标识符指定自己的依赖,而 AMD 加载器会在所有依赖模块加载完毕后 立即调用模块工厂函数。与 CommonJS 不同,AMD 支持可选地为模块指定字符串标识符。

// ID 为'moduleA'的模块定义。moduleA 依赖 moduleB,
// moduleB 会异步加载
define('moduleA', ['moduleB'], function(moduleB) { 
 return { 
 stuff: moduleB.doStuff(); 
 }; 
});

AMD 也支持 require 和 exports 对象,通过它们可以在 AMD 模块工厂函数内部定义 CommonJS 风格的模块。

define('moduleA', ['require', 'exports'], function(require, exports) { 
 var moduleB = require('moduleB'); 
 exports.stuff = moduleB.doStuff(); 
}); 
// 动态依赖也是通过这种方式支持的:
define('moduleA', ['require'], function(require) { 
 if (condition) { 
 var moduleB = require('moduleB'); 
 } 
}); 

26.3.3 通用模块定义UMD

为了统一 CommonJS 和 AMD 生态系统,通用模块定义(UMD,Universal Module Definition)规范 应运而生。UMD 可用于创建这两个系统都可以使用的模块代码。本质上,UMD 定义的模块会在启动时 检测要使用哪个模块系统,然后进行适当配置,并把所有逻辑包装在一个立即调用的函数表达式(IIFE) 中。虽然这种组合并不完美,但在很多场景下足以实现两个生态的共存。

26.4 使用 ES6 模块

ES6 最大的一个改进就是引入了模块规范。这个规范全方位简化了之前出现的模块加载器,原生浏 览器支持意味着加载器及其他预处理都不再必要。

26.4.1 模块标签及定义

26.4.2 模块加载

模块文件按需加载,且后续模块的请求会因为每个依 赖模块的网络延迟而同步延迟。即,如果 moduleA 依赖 moduleB,moduleB 依赖 moduleC。浏览器在 对 moduleB 的请求完成之前并不知道要请求 moduleC。这种加载方式效率很高,也不需要外部工具, 但加载大型应用程序的深度依赖图可能要花费很长时间

26.4.3 模块行为

ECMAScript 6 模块借用了 CommonJS 和 AMD 的很多优秀特性。下面简单列举一些。
 模块代码只在加载后执行。
 模块只能加载一次。
 模块是单例。
 模块可以定义公共接口,其他模块可以基于这个公共接口观察和交互。
 模块可以请求加载其他模块。
 支持循环依赖。

ES6 模块系统也增加了一些新行为。
 ES6 模块默认在严格模式下执行。
 ES6 模块不共享全局命名空间。
 模块顶级 this 的值是 undefined(常规脚本中是 window)。
 模块中的 var 声明不会添加到 window 对象。
 ES6 模块是异步加载和执行的

26.4.4 模块导出

 // export 关键字用于声明一个值为命名导出。导出语句必须在模块顶级,不能嵌套在某个块中:
// 允许
export ... 
// 不允许
if (condition) { 
 export ... 
} 
// 导出值对模块内部 JavaScript 的执行没有直接影响,因此 export 语句与导出值的相对位置或者
// export 关键字在模块中出现的顺序没有限制。export 语句甚至可以出现在它要导出的值之前:
// 允许
const foo = 'foo'; 
export { foo }; 
// 允许
export const foo = 'foo'; 
// 允许,但应该避免
export { foo }; 
const foo = 'foo';

26.4.5 模块导入

模块可以通过使用 import 关键字使用其他模块导出的值。与 export 类似,import 必须出现在 模块的顶级。import 语句被提升到模块顶部。因此,与 export 关键字类似,import 语句与使用导入值的语句 的相对位置并不重要。不过,还是推荐把导入语句放在模块顶部:

// 允许
import ... 
// 不允许
if (condition) { 
 import ... 
} 

// 允许
import { foo } from './fooModule.js'; 
console.log(foo); // 'foo' 
// 允许,但应该避免
console.log(foo); // 'foo' 
import { foo } from './fooModule.js';

26.5 小结

模块模式是管理复杂性的永恒工具。开发者可以通过它创建逻辑彼此独立的代码段,在这些代码段 之间声明依赖,并将它们连接在一起。此外,这种模式也是经证明能够优雅扩展到任意复杂度且跨平台 的方案。 多年以来,CommonJS 和 AMD 这两个分别针对服务器端环境和受延迟限制的客户端环境的模块系 统长期分裂。两个系统都获得了爆炸性增强,但为它们编写的代码则在很多方面不一致,经常也会带有 冗余的样板代码。而且,这两个系统都没有在浏览器中实现。缺乏兼容导致出现了相关工具,从而让在 浏览器中实现模块模式成为可能。 ECMAScript 6 规范重新定义了浏览器模块,集之前两个系统之长于一身,并通过更简单的声明性语 法暴露出来。浏览器对原生模块的支持越来越好,但也提供了稳健的工具以实现从不支持到支持 ES6 模块的过渡。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值