一、导入:

  • 模块中命名的导出:
    • import {x [as y], …} from “module”
  • 默认的导出:
    • import x from “module”
    • import {default as x} from “module”
  • 所有:
    • import * as obj from “module”
  • 导入模块(它的代码,并运行),但不要将其赋值给变量:
    • import “module”

二、导出:

  • 在声明一个 class/function/… 之前:
    • export [default] class/function/variable …
  • 独立的导出:
    • export {x [as y], …}.
  • 重新导出:
    • export {x [as y], …} from “module”
    • export * from “module”(不会重新导出默认的导出)。
    • export {default [as y]} from “module”(重新导出默认的导出)。

在声明前导出

一、我们可以通过在声明之前放置export来标记任意声明为导出,无论声明的是变量,函数还是类都可以。

| 【示例】这里的所有导出均有效:```javascript // 导出数组 export let months = [‘Jan’, ‘Feb’, ‘Mar’,’Apr’, ‘Aug’, ‘Sep’, ‘Oct’, ‘Nov’, ‘Dec’];

// 导出 const 声明的变量 export const MODULES_BECAME_STANDARD_YEAR = 2015;

// 导出类 export class User { constructor(name) { this.name = name; } }

  1. |
  2. | --- |
  3. 二、导出 class/function 后没有分号<br />1、在类或者函数前的export不会让它们变成函数表达式。尽管被导出了,但它仍然是一个函数声明。<br />2、大部分 JavaScript 样式指南都不建议在函数和类声明后使用分号。<br />3、这就是为什么在export classexport function的末尾不需要加分号:
  4. | 【示例】```javascript
  5. export function sayHi(user) {
  6. alert(`Hello, ${user}!`);
  7. } // 在这里没有分号 ;

| | —- |

导出与声明分开

一、我们还可以将export分开放置。

| 【示例】先声明函数,然后再导出它们:``javascript // 📁 say.js function sayHi(user) { alert(Hello, ${user}!`); }

function sayBye(user) { alert(Bye, ${user}!); }

export {sayHi, sayBye}; // 导出变量列表

 |
| --- |

<a name="rX92b"></a>
# Import
<a name="u1ohf"></a>
## Import *
一、通常,我们把要导入的东西列在花括号import {...}中,就像这样:

| 【示例】```javascript
// 📁 main.js
import {sayHi, sayBye} from './say.js';

sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!

| | —- |

二、我们把import/export语句放在脚本的顶部或底部,都没关系。

| 【示例】从技术上讲,下面这样的代码没有问题:```javascript sayHi();

// …

import {sayHi} from ‘./say.js’; // 在文件底部导入

 |
| --- |

三、请注意在{...}中的 import/export 语句无效。

| 【示例】像这样的有条件的导入是无效的:```javascript
if (something) {
  import {sayHi} from "./say.js"; // Error: import must be at top level
}

| | —- |

import * as

一、在import、export语句的大括号中,可以使用as关键字跟一个新的名字,来改变在顶级模块中将要使用的功能的标识名字。
二、如果有很多要导入的内容,我们可以使用import * as 将所有内容导入为一个对象。

| 【示例】```javascript // 📁 main.js import * as say from ‘./say.js’;

say.sayHi(‘John’); say.sayBye(‘John’);

 |
| --- |

三、乍一看,“通通导入”写起来很短,但是我们通常为什么要明确列出我们需要导入的内容?<br />这里有几个原因。

1. 现代的构建工具([webpack](http://webpack.github.io/)和其他工具)将模块打包到一起并对其进行tree-shaking优化,以加快加载速度并删除未使用的代码。
| 【示例】我们向我们的项目里添加一个第三方库say.js,它具有许多函数:```javascript
// 📁 say.js
export function sayHi() { ... }
export function sayBye() { ... }
export function becomeSilent() { ... }

如果我们只在我们的项目里使用了say.js中的一个函数:```javascript // 📁 main.js import {sayHi} from ‘./say.js’;……

那么,优化器(optimizer)就会检测到它,并从打包好的代码中删除那些未被使用的函数,从而使构建更小。这就是所谓的“摇树(tree-shaking)”。 |
| --- |

2. 明确列出要导入的内容会使得名称较短。
| 【示例】sayHi()而不是say.sayHi() |
| --- |

3. 导入的显式列表可以更好地概述代码结构:使用的内容和位置。它使得代码支持重构,并且重构起来更容易。
<a name="PvWPC"></a>
## Import  as
一、我们也可以使用as让导入具有不同的名字。

| 【示例】将sayHi导入到局部变量hi,将sayBye导入到bye:```javascript
// 📁 main.js
import {sayHi as hi, sayBye as bye} from './say.js';

hi('John'); // Hello, John!
bye('John'); // Bye, John!

| | —- |

Export 导出

一、导出类型
1、由named exports组成:
(1)每个项目(无论是函数、常亮等)在导出时都由其名称引用,并且该名称也用于在导入时引用它。
2、default export
这样可以很容易地使用模块提供默认功能,并且还可以帮助JavaScript模块与现有的CommonJS和AMD模块系统进行互操作。

命名导出:Export as

一、导出也具有类似的语法。

| 【示例】将函数导出为hi和bye:```javascript // 📁 say.js … export {sayHi as hi, sayBye as bye};

<br /><br />现在hi和bye是在外面使用时的正式名称:```javascript
// 📁 main.js
import * as say from './say.js';

say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!

| | —- |

默认导出:Export default

一、在实际中,主要有两种模块。

  • 包含库或函数包的模块,像上面的say.js。
  • 声明单个实体的模块,例如模块user.js仅导出class User。

1、大部分情况下,开发者倾向于使用第二种方式,以便每个“东西”都存在于它自己的模块中。
2、当然,这需要大量文件,因为每个东西都需要自己的模块,但这根本不是问题。实际上,如果文件具有良好的命名,并且文件夹结构得当,那么代码导航(navigation)会变得更容易。
3、模块提供了一个特殊的默认导出export default语法,以使“一个模块只做一件事”的方式看起来更好。
二、将export default放在要导出的实体前:

| 【示例】```javascript // 📁 user.js export default class User { // 只需要添加 “default” 即可 constructor(name) { this.name = name; } }

1、每个文件可能只有一个export default<br />2、然后将其导入而不需要花括号:```javascript
// 📁 main.js
import User from './user.js'; // 不需要花括号 {User},只需要写成 User 即可

new User('John');

| | —- |

三、import命名的导出时需要花括号,而import默认的导出时不需要花括号。

命名的导出 默认的导出
export class User {…} export default class User {…}
import {User} from … import User from …

四、从技术上讲,我们可以在一个模块中同时有默认的导出和命名的导出,但是实际上人们通常不会混合使用它们。模块要么是命名的导出要么是默认的导出。
五、由于每个文件最多只能有一个默认的导出,因此导出的实体可能没有名称。

| 【示例】下面这些都是完全有效的默认的导出:```javascript export default class { // 没有类名 constructor() { … } }

```javascript
export default function(user) { // 没有函数名
  alert(`Hello, ${user}!`);
}
// 导出单个值,而不使用变量
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

| | —- |

六、不指定名称是可以的,因为每个文件只有一个export default,因此不带花括号的import知道要导入的内容是什么。

| 【示例】如果没有default,这样的导出将会出错:```javascript export class { // Error!(非默认的导出需要名称) constructor() {} }

 |
| --- |

<a name="ePRD1"></a>
### “default” 名称
一、在某些情况下,default关键词被用于引用默认的导出。

| 【示例】要将函数与其定义分开导出:```javascript
function sayHi(user) {
  alert(`Hello, ${user}!`);
}

// 就像我们在函数之前添加了 "export default" 一样
export {sayHi as default};

| | —- |

| 【示例】假设模块user.js导出了一个主要的默认的导出和一些命名的导出(这种情况很少见,但确实会发生):```javascript // 📁 user.js export default class User { constructor(name) { this.name = name; } }

export function sayHi(user) { alert(Hello, ${user}!); }

这是导入默认的导出以及命名的导出的方法:```javascript
// 📁 main.js
import {default as User, sayHi} from './user.js';

new User('John');

| | —- |

| 【示例】如果我们将所有东西作为一个对象导入,那么default属性正是默认的导出:```javascript // 📁 main.js import as user from ‘./user.js’;

let User = user.default; // 默认的导出 new User(‘John’);

 |
| --- |


<a name="ordGz"></a>
### 我应该使用默认的导出吗?
一、命名的导出是明确的。它们确切地命名了它们要导出的内容,因此我们能从它们获得这些信息。<br />二、命名的导出会强制我们使用正确的名称进行导入:

| 【示例】```javascript
import {User} from './user.js';
// 导入 {MyUser} 不起作用,导入名字必须为 {User}

| | —- |

三、对于默认的导出,我们总是在导入时选择名称:

| 【示例】```javascript import User from ‘./user.js’; // 有效 import MyUser from ‘./user.js’; // 也有效 // 使用任何名称导入都没有问题

 |
| --- |

1、因此,团队成员可能会使用不同的名称来导入相同的内容,这不好。<br />2、通常,为了避免这种情况并使代码保持一致,可以遵从这条规则,即导入的变量应与文件名相对应。

| 【示例】```javascript
import User from './user.js';
import LoginForm from './loginForm.js';
import func from '/path/to/func.js';

| | —- |

3、但是,一些团队仍然认为这是默认的导出的严重缺陷。因此,他们更倾向于始终使用命名的导出。即使只导出一个东西,也仍然使用命名的导出,而不是默认的导出。这也使得重新导出更容易。

避免命名冲突

一、避免命名冲突的方式:

  • 重新导出
  • 创建模块对象
  • 模块与类(class)

重新导出

一、“重新导出(Re-export)”语法export … from …允许导入内容,并立即将其导出(可能是用的是其他的名字)

| 【示例】就像这样```javascript export {sayHi} from ‘./say.js’; // 重新导出 sayHi

export {default as User} from ‘./user.js’; // 重新导出 default

 |
| --- |

二、使用场景:

| 【示例】想象一下,我们正在编写一个 “package”:一个包含大量模块的文件夹,其中一些功能是导出到外部的(像 NPM 这样的工具允许我们发布和分发这样的 package,但我们不是必须要去使用它们),并且其中一些模块仅仅是供其他 package 中的模块内部使用的 “helpers”。<br />一、文件结构可能是这样的:```javascript
auth/
    index.js
    user.js
    helpers.js
    tests/
        login.js
    providers/
        github.js
        facebook.js
        ...

二、我们想通过单个入口,即“主文件”auth/index.js来公开 package 的功能,进而可以像下面这样使用我们的 package:```javascript import {login, logout} from ‘auth/index.js’

1、我们的想法是,使用我们 package 的开发者,不应该干预其内部结构,不应该搜索我们 package 的文件夹中的文件。我们只在auth/index.js中导出必须的内容,并保持其他内容“不可见”。<br />三、由于实际导出的功能分散在 package 中,所以我们可以将它们导入到auth/index.js,然后再从中导出它们:```javascript
// 📁 auth/index.js

// 导入 login/logout 然后立即导出它们
import {login, logout} from './helpers.js';
export {login, logout};

// 将默认导出导入为 User,然后导出它
import User from './user.js';
export {User};
...

四、现在使用我们 package 的人可以import {login} from “auth/index.js”。
五、语法export … from …只是下面这种导入-导出的简写:```javascript // 📁 auth/index.js // 导入 login/logout 然后立即导出它们 export {login, logout} from ‘./helpers.js’;

// 将默认导出导入为 User,然后导出它 export {default as User} from ‘./user.js’; …

 |
| --- |

<a name="Gqq08"></a>
### 重新导出默认导出
一、重新导出时,默认导出需要单独处理。

| 【示例】假设我们有一个user.js脚本,其中写了export default class User,并且我们想重新导出类User:```javascript
// 📁 user.js
export default class User {
  // ...
}

一、我们可能会遇到两个问题:
1. export User from ‘./user.js’无效。这会导致一个语法错误。要重新导出默认导出,我们必须明确写出export {default as User},就像上面的例子中那样。
1. export from ‘./user.js’重新导出只导出了命名的导出,但是忽略了默认的导出。如果我们想将命名的导出和默认的导出都重新导出,那么需要两条语句:
```javascript export
from ‘./user.js’; // 重新导出命名的导出 export {default} from ‘./user.js’; // 重新导出默认的导出

二、重新导出一个默认导出的这种奇怪现象,是某些开发者不喜欢默认导出,而是喜欢命名的导出的原因之一。 |
| --- |

<a name="XZJdW"></a>
### 合并模块
一、有时候会想要将模块聚合在一起。如果有多个级别的依赖项,可能需要将多个子模块组合到一个父模块中。<br />二、父模块导出语法:
```javascript
export * from 'x.mjs'
export { name } from 'x.mjs'

1、实际是导入后跟导出的简写,即“我导入模块x.mjs,然后重新导出部分或全部导出”。

| 【示例】```javascript modules/ canvas.mjs shapes.mjs shapes/ circle.mjs square.mjs triangle.mjs

在每个子模块中,输出具有相同的形式,如`export { Square }`<br />1、原先的访问模式```javascript
import { Square } from './modules/square.mjs';
import { Circle } from './modules/circle.mjs';
import { Triangle } from './modules/triangle.mjs';

2、改进```javascript export { Square } from ‘/js-examples/modules/module-aggregation/modules/shapes/square.mjs’; export { Triangle } from ‘/js-examples/modules/module-aggregation/modules/shapes/triangle.mjs’; export { Circle } from ‘/js-examples/modules/module-aggregation/modules/shapes/circle.mjs’;

> 备注:即使 shapes.mjs 文件位于 modules 目录中,我们仍然需要相对于模块根目录编写这些 URL,因此需要 /modules/。 这是使用 JavaScript 模块时混淆的常见原因。

> 备注:shapes.mjs 中引用的导出基本上通过文件重定向,并且实际上并不存在,因此您将无法在同一文件中编写任何有用的相关代码。

```javascript
import { Square, Circle, Triangle } from './modules/shapes.mjs';

| | —- |

创建模块对象

一、导入每一个模块功能到一个模块功能对象上

impoort * as Module from './modules.mjs'

Module.function1();

| 【示例】```javascript import * as Module from ‘/modules/module.mjs’;

1、这将获取module.mjs中所有可用的导出,并使它们可用作为对象模块的成员使用,从而有效地为其提供自己的命名空间```javascript
Module.function1()
Module.function2()
etc

| | —- |

| 【示例】```javascript // in square.mjs、circle.mjs、triangle.mjs // 导出 export { name, draw, reportArea, reportPerimeter };

// in main.mjs // 导入 import as Canvas from ‘./modules/canvas.mjs’; import as Square from ‘/./modules/square.mjs’; import as Circle from ‘./modules/circle.mjs’; import as Triangle from ‘./modules/triangle.mjs’; // 访问指定对象名称下面的模块导入 let square1 = Square.draw(myCanvas.ctx, 50, 50, 100, ‘blue’); Square.reportArea(square1.length, reportList); Square.reportPerimeter(square1.length, reportList);

 |
| --- |

<a name="LDpxQ"></a>
##  模块与类(class)
一、如果已经以面向对象的方式编写了模块代码,那么导出和导入类在避免代码冲突方面尤其有用。

| 【示例】```javascript
// in square.mjs
class Square {
  constructor(ctx, listId, length, x, y, color) {
    ...
  }

  draw() {
    ...
  }

  ...
}

// 导出
export { Square };
// in main.mjs
// 导入
import { Square } from './modules/square.mjs';
// 使用该类绘制我们的方块
let square1 = new Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');
square1.draw();
square1.reportArea();
square1.reportPerimeter();

| | —- |

“静态”导入

一、语法非常简单且严格。
1、首先,我们不能动态生成import的任何参数。
(1)模块路径必须是原始类型字符串,不能是函数调用

| 【示例】下面这样的import行不通```javascript import … from getModuleName(); // Error, only from “string” is allowed

 |
| --- |

2、其次,我们无法根据条件或者在运行时导入:

| 【示例】```javascript
if(...) {
  import ...; // Error, not allowed!
}

{
  import ...; // Error, we can't put import in any block
}

| | —- |

二、这是因为import/export旨在提供代码结构的主干。

  • 这样便于分析代码结构,可以收集模块,可以使用特殊工具将收集的模块打包到一个文件中,可以删除未使用的导出(“tree-shakeng”)。
  • 这些只有在import/export结构简单且固定的情况下才能够实现。