# 模块(Encapsulating Code With Modules)


JavaScript 采用 “共享一切” 的代码加载方式是该语言中最令人迷惑且容易出错的方面之一。其它语言使用包(package)的概念来定义代码的作用范围,然而在 ECMAScript 6 之前,每个 JavaScript 文件中定义的内容都由全局作用域共享。当 web 应用变得复杂并需要书写更多的 JavaScript 代码时,上述加载方式会出现命名冲突或安全方面的问题。ECMAScript 6 的目标之一就是解决作用域的问题并将 JavaScript 应用中的代码整理得更有条理,于是模块应运而生。




### 本章小结
* [什么是模块?](#What-are-Modules)
* [输出的基本概念](#Basic-Exporting)
* [引入的基本概念](#Basic-Importing)
* [export 和 import 的重命名](#Renaming-Exports-and-Imports)
* [模块中的默认值](#Default-Values-in-Modules)
* [绑定的再输出](#Re-exporting-a-Binding)
* [全局引入](#Importing-Without-Bindings)
* [模块加载](#Loading-Modules)
* [总结](#Summary)




### 什么是模块?(What are Modules?)


模块是指采取不同于现有加载方式的 JavaScript 文件(与 script 这种传统的加载模式相对)。这种方式很有必要,因为它和 script 使用不同的语义:

1. 模块中的代码自动运行在严格模式下,并无任何办法修改为非严格模式。
2. 模块中的顶级(top level)变量不会被添加到全局作用域中。它们只存在于各自的模块中的顶级作用域。
3. 模块顶级作用域中的 this 为 undefined 。
4. 模块不允许存在 HTML 式的注释(JavaScript 历史悠久的遗留特性)。
5. 模块必须输出可被模块外部代码使用的相关内容。
6. 模块可能会引入其它模块中的绑定。

这些差异刚开始看上去觉得并不是很大,不过它们体现了 JavaScript 关于加载和计算代码的显著变更,本章随后我会解释它们。模块真正的好处在于可以输出和引入需要的绑定,而不是文件中的所有内容。理解输出和引入是领悟模块与 script 之间差异的基础。




### 引入的基本概念(Basic Exporting)


你可以使用 export 关键字来对外暴露模块中的部分代码。一般情况下,你可以在任何变量,函数或类声明之前添加这个关键字来输出它们,像这样:

```js
// 输出变量
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;

// 输出函数
export function sum(num1, num2) {
return num1 + num1;
}

// 输出类
export class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
}

// 该函数是模块私有的
function subtract(num1, num2) {
return num1 - num2;
}

// 定义一个函数...
function multiply(num1, num2) {
return num1 * num2;
}

// ...并在之后输出它
export { multiply };
```

该例中需要注意一些要点。首先,除 export 关键字之外,所有的声明和传统的形式完全一致。每个输出的函数或类都有一个名称;因为名称是必须的。除非你使用了 default 关键字(在 “模块中的默认值” 一节讨论),否则你不能使用该语法来输出匿名函数或类。

其次,multiply() 函数并未在定义的时候被输出。这是因为你不必每次都要输出一个声明:你可以输出一个引用。最后,该例中并未输出 subtract() 函数。该函数在外部是不可见的,因为任何未显式输出的变量,函数或类都是模块私有的。




### 引入的基本概念(Basic Importing)


一旦你有了包含输出内容的模块,你可以在另一个模块内使用 import 关键字来获取它的相关功能。import 语句包含两部分内容,分别为引入的标识符和输出这些标识符的模块。以下是该语句的基本形式:

```js
import { identifier1, identifier2 } from "./example.js";
```

import 之后的花括号表示从模块中引入的绑定。from 关键字表示从哪个模块引入这些绑定。模块由一个包含模块路径的字符串表示(称为模块指示符,module sepcifier)。浏览器中的 `



```

本例中的首个 `






```

这三个 `

```

该例中异步加载了两个模块。仅仅观察模块中的代码无法确定模块的执行顺序。如果 module1.js 首先下载完毕(包括它引入的资源),那么它会首先执行。module2.js 也是同理。




##### 在 worker 中加载模块(Loading Modules as Workers)


worker,包括 web worker 和 service worker,会在网页之外的上下文中执行 JavaScript 代码。创建一个新的 worker 需要 worker 实例(或类)并传递 JavaScript 文件的位置。worker 默认的加载机制是将文件视为 script,像这样:

```js
// 以 script 的方式加载 script.js
let worker = new Worker("script.js");
```

为了支持模块的加载,负责 HTML 标准的开发者们给构造函数添加了第二个参数。这个参数是包含 type 属性并且默认值为 script 的对象。你可以将 type 设定为 "module" 来加载模块。

```js
// 以模块的方式加载 module.js
let worker = new Worker("module.js", { type: "module" });
```

该例将第二个参数中的 type 属性值设定为 "module",因此 module.js 以模块而不是 script 的形式加载(type 属性是对 `