ES6 模块是一个仅在严格模式下执行的 JavaScript 文件。这意味着模块中声明的任何变量或函数都不会自动添加到全局范围中。

在 Web 浏览器上执行模块

首先,创建一个名为 message.js 的新文件并添加以下代码:




export let message = 'ES6 Modules';

message.js 是 ES6 中包含 message 变量的模块。export 语句将消息变量公开给其他模块。

其次,创建另一个名为 app.js 的新文件,该文件使用 message.js 模块。app.js 模块创建一个新的标题 1 (h1) 元素并将其附加到 HTML 页面。import 语句从 message.js 模块导入消息变量。




import { message } from './message.js'
const h1 = document.createElement('h1');
h1.textContent = message
document.body.appendChild(h1)

第三,创建一个使用 app.js 模块的新 HTML 页面:



<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>ES6 Modules</title>
</head>
<body>
<script type="module" src="./app.js"></script>
</body>
</html>

请注意,我们在 script 标签中使用了 type="module" 来加载 app.js 模块。如果我们在 Web 浏览器上查看该页面,我们将看到以下页面:

es6 导入1个目录 es6文件_ES6

让我们更详细地检查导出和导入语句。

Exporting

要导出变量、函数或类,请将 export 关键字放在其前面,如下所示:




// log.js
export let message = 'Hi';
export function getMessage() {
  return message;
}
export function setMessage(msg) {
  message = msg;
}
export class Logger {
}

在这个例子中,我们有一个带有一个变量、两个函数和一个类的 log.js 模块。我们使用 export 关键字来导出模块中的所有标识符。

请注意,export 关键字要求函数或类具有要导出的名称。我们不能使用此语法导出匿名函数或类。

JavaScript 允许我们先定义变量、函数或类,然后再将其导出,如下所示:




// foo.js
function foo() {
   console.log('foo');
}
function bar() {
  console.log('bar');
}
export foo;

在本例中,我们先定义了 foo() 函数,然后将其导出。由于我们没有导出 bar() 函数,因此,我们无法在其他模块中访问它。bar() 函数在模块外部是不可访问的,或者我们说它是私有的。

Importing

一旦定义了带有导出的模块,就可以使用 import 关键字访问另一个模块中导出的变量、函数和类。下面说明了语法:




import { what, ever } from './other_module.js';

在这种语法中:

首先,在大括号内指定要导入的内容,称为绑定。

然后,指定从中导入给定绑定的模块。

请注意,当我们从模块导入绑定时,绑定的行为就像使用 const 定义的一样。这意味着我们不能拥有另一个具有相同名称的标识符或更改绑定的值。

请参见以下示例:




// greeting.js
export let message = 'Hi';
export function setMessage(msg) {
  message = msg;
}

导入message变量和 setMessage() 函数时,可以使用 setMessage() 函数更改message变量的值,如下所示:




// app.js
import {message, setMessage } from './greeting.js';
console.log(message); // 'Hi'
setMessage('Hello');
console.log(message); // 'Hello'

但是,我们不能直接更改message变量的值。以下表达式会导致错误:




message = 'Hallo'; // error

在幕后,当我们调用 setMessage() 函数时。JavaScript 返回到 greeting.js 模块并在其中执行代码并更改消息变量。然后,更改会自动反映在导入的消息绑定上。

app.js 中的消息绑定是导出消息标识符的本地名称。所以,基本上 app.js 和 greeting.js 模块中的消息变量是不一样的。

导入单个绑定

假设我们有一个带有 foo 变量的模块,如下所示:




// foo.js
export foo = 10;

然后,在另一个模块中,我们可以重用 foo 变量:




// app.js
import { foo } from './foo.js';
console.log(foo); // 10;

但是,我们不能更改 foo 的值。如果我们尝试这样做,我们会得到一个错误:




foo = 20; // throws an error

导入多个绑定

假设我们有 cal.js 模块,如下所示:




// cal.js
export let a = 10,
           b = 20,
           result = 0;
export function sum() {
  result = a + b;
  return result;
}
export function multiply() {
  result = a * b;
  return result;
}

并且我们想从 cal.js 中导入这些绑定,我们可以显式列出它们,如下所示:




import {a, b, result, sum, multiply } from './cal.js';
sum();
console.log(result); // 30
multiply();
console.log(result); // 200

将整个模块作为对象导入

要将模块中的所有内容作为单个对象导入,请使用星号 (*) 模式,如下所示:




import * as cal from './cal.js';

在此示例中,我们从 cal.js 模块中导入了所有绑定作为 cal 对象。 

在这种情况下,所有绑定都成为 cal 对象的属性,因此,我们可以访问它们,如下所示:




cal.a;
cal.b;
cal.sum();

这种导入称为命名空间导入。

重要的是要记住,导入的模块只执行一次,甚至多次导入。考虑这个例子:




import { a } from './cal.js';
import { b } from './cal.js';
import {result} from './cal.js';

在第一个 import 语句之后,cal.js 模块被执行并加载到内存中,并在后续 import 语句引用的任何时候被重用。

输入输出的报表限制

请注意,我们必须在其他语句和函数之外使用 import 或 export 语句。以下示例导致 SyntaxError:




if( requiredSum ) {
   export sum;
}

因为我们在 if 语句中使用了 export 语句。同样,以下 import 语句也会导致 SyntaxError:




function importSum() {
   import {sum} from './cal.js';
}

因为我们在函数内部使用了 import 语句。

错误的原因是 JavaScript 必须静态确定要导出和导入的内容。

请注意,ES2020 引入了类似函数的对象 import(),它允许我们动态导入模块。

Aliasing

JavaScript 允许我们在导出和导入时为变量、函数或类创建别名。请参阅以下 math.js 模块:




// math.js  
function add( a, b ) {
   return a + b;
}
export { add as sum };

在此示例中,我们没有导出 add() 函数,而是使用 as 关键字为 sum() 函数分配别名。

所以当我们从 math.js 模块中导入 add() 函数时,我们必须使用 sum 来代替:




import { sum } from './math.js';

如果要在导入时使用不同的名称,可以使用 as 关键字,如下所示:




import {sum as total} from './math.js';

重新导出绑定

可以导出我们已导入的绑定。这称为重新导出。例如:




import { sum } from './math.js';
export { sum };

在此示例中,我们从 math.js 模块导入 sum 并重新导出。以下语句等效于上述语句:




export {sum} from './math.js';

如果我们想在重新导出之前重命名绑定,请使用 as 关键字。 

以下示例从 math.js 模块导入 sum 并将其重新导出为 add。




export { sum as add } from './math.js';

如果要从另一个模块导出所有绑定,可以使用星号 (*):




export * from './cal.js';

无绑定导入

有时,我们想要开发一个不导出任何内容的模块,例如,我们可能想要向内置对象(如 Array)添加新方法。




// array.js
if (!Array.prototype.contain) {
  Array.prototype.contain = function(e) {
    // contain implementation
    // ...
  }
}

现在,我们可以在没有任何绑定的情况下导入模块并使用 array.js 模块中定义的 contains() 方法,如下所示:




import './array.js';
[1,2,3].contain(2); // true

默认导出

一个模块可以有一个且只有一个默认导出。默认导出更容易导入。模块的默认值可以是变量、函数或类。

以下是带有默认导出的 sort.js 模块。




// sort.js
export default function(arr) {
  // sorting here
}

请注意,我们不需要为函数指定名称,因为模块代表函数名称。




import sort from sort.js;
sort([2,1,3]);

如上所见,排序标识符代表 sort.js 模块的默认功能。请注意,我们没有在排序标识符周围使用花括号 {}。

让我们更改 sort.js 模块以包含默认导出以及非默认导出:




// sort.js
export default function(arr) {
  // sorting here
}
export function heapSort(arr) {
  // heapsort
}

要同时导入默认和非默认绑定,请在 import 关键字之后使用以下规则指定绑定列表:

默认绑定必须先出现。

非默认绑定必须用花括号括起来。

请参见以下示例:




import sort, {heapSort} from './sort.js';
sort([2,1,3]);
heapSort([3,1,2]);

要重命名默认导出,我们还可以使用 as 关键字,如下所示:




import { default as quicksort, heapSort} from './sort.js';

在今天的教程中,我们了解了 ES6 模块以及如何从一个模块导出绑定并将它们导入另一个模块。