ES6 模块中的 `export` 和 `import`

我们来详细解析 JavaScript ES6 模块中的 exportimport

exportimport 是 ES6 模块系统的核心,它们使得 JavaScript 代码可以被分割成可重用的、独立的文件(模块),极大地改善了代码的组织结构、可维护性和依赖管理。

核心概念

  • 模块化: 每个文件就是一个独立的模块。

  • 独立作用域: 每个模块内部都有自己的作用域,模块内的变量、函数、类在外部默认是不可见的。

  • 静态解析: importexport 命令只能在模块的顶层使用,不能在代码块(如 if 语句)或函数内部使用。这是因为 ES6 模块在编译时(静态地)就能确定模块的依赖关系,以及导入和导出了哪些变量。


export:从模块中导出

export 命令用于规定模块的对外接口。一个模块可以有多个 export。导出方式主要分为两种:命名导出(Named Exports)默认导出(Default Export)

1. 命名导出 (Named Exports)

命名导出允许你用指定的名字导出一个或多个变量、函数或类。

方式一:在声明时直接导出

  
// a.js
  
export const name = 'Alice';
  
export let age = 25;
  

  
export function sayHello() {
  
  console.log('Hello!');
  
}
  

  
export class User {
  
  constructor(name) {
  
    this.name = name;
  
  }
  
}
  

方式二:使用大括号统一导出

这种方式更加清晰,可以一目了然地看到模块导出了哪些内容。

  
// b.js
  
const name = 'Bob';
  
function sayGoodbye() {
  
  console.log('Goodbye!');
  
}
  

  
// 在文件末尾统一导出
  
export { name, sayGoodbye };
  

方式三:导出时重命名

如果你想用不同的名字导出,可以使用 as 关键字。

  
// c.js
  
function greet() {
  
  console.log('Greetings!');
  
}
  

  
// 将 greet 函数以 hi 的名字导出
  
export { greet as hi };
  

2. 默认导出 (Default Export)

每个模块只能有一个默认导出。默认导出时,导入方可以为其指定任意名称。这对于模块只提供单一功能或主要功能的场景非常方便。

  
// d.js
  
export default function() {
  
  console.log('This is the default export.');
  
}
  

  
// 或者导出一个类
  
// export default class User { ... }
  

  
// 或者导出一个变量
  
// const myConfig = { ... };
  
// export default myConfig;
  

注意: export default 后面跟的是一个值(表达式),所以不能写成 export default const name = '...';,而应该写成 const name = '...'; export default name;

3. 混合使用

一个模块可以同时使用命名导出和默认导出。

  
// e.js
  
export default function mainFunction() {
  
  // ...
  
}
  

  
export const version = '1.0';
  
export function helper() {
  
  // ...
  
}
  

import:从模块中导入

import 命令用于加载其他模块提供的功能。

1. 导入命名导出的模块

导入命名导出的成员时,必须使用大括号 {},并且变量名必须与导出时的名称一致。

  
// main.js
  
import { name, sayHello } from './a.js';
  

  
console.log(name); // 'Alice'
  
sayHello(); // 'Hello!'
  

导入时重命名

如果导入的变量名与当前作用域的变量冲突,或者你想要一个更具描述性的名字,可以使用 as 关键字。

  
// main.js
  
import { name as userName, sayHello as greet } from './a.js';
  

  
console.log(userName); // 'Alice'
  
greet(); // 'Hello!'
  

2. 导入默认导出的模块

导入默认导出的成员时,不需要使用大括号,并且可以为其指定任意名称。

  
// main.js
  
import myCustomFunction from './d.js';
  

  
myCustomFunction(); // 'This is the default export.'
  

3. 导入所有命名导出的成员(通配符 *

你可以使用星号 (*) 将一个模块中所有命名导出的成员作为一个对象导入。

  
// main.js
  
import * as utils from './a.js'; // utils 是你自定义的对象名
  

  
console.log(utils.name); // 'Alice'
  
utils.sayHello(); // 'Hello!'
  
console.log(utils.age); // 25
  

注意: import * 不会包含 export default 导出的成员。

4. 混合导入

当一个模块同时有默认导出和命名导出时,可以这样导入:

  
// main.js
  
import mainFunc, { version, helper } from './e.js';
  

  
mainFunc();
  
console.log(version); // '1.0'
  
helper();
  

mainFunc 是对 e.js 模块默认导出的引用,{ version, helper } 是对命名导出的引用。

5. 仅执行模块(副作用导入)

有时你可能只想执行一个模块中的代码,而不需要导入任何东西(例如,这个模块可能只是向 window 添加了一些 polyfill)。这种情况下,可以直接 import 该文件。

  
// main.js
  
import './polyfill.js'; // 仅执行 polyfill.js 文件中的代码
  

export ... from:模块的转发

这个语法可以将另一个模块的接口聚合到当前模块中,并直接导出,而无需先 importexport

转发命名导出

  
// aggregator.js
  

  
// 将 a.js 中的 name 和 sayHello 导出
  
export { name, sayHello } from './a.js';
  

  
// 转发时重命名
  
export { hi as greet } from './c.js';
  

转发所有命名导出

  
// aggregator.js
  
export * from './b.js'; // 转发 b.js 的所有命名导出
  

转发默认导出

转发默认导出需要显式命名。

  
// aggregator.js
  
export { default as myDefaultFunction } from './d.js';
  

注意: export * from './module.js' 不会转发 module.js 的默认导出。

总结与关键点

| 特性 | export (导出) | import (导入) |

| :--- | :--- | :--- |

| 类型 | 命名导出 (export { a }) / 默认导出 (export default b) | 命名导入 (import { a }) / 默认导入 (import b) |

| 数量 | 命名导出可有多个,默认导出只能有一个。 | 可以同时导入命名和默认成员。 |

| 命名 | 命名导出必须使用导出时的名字;默认导出不需要。 | 命名导入必须使用导出时的名字(可用 as 重命名);默认导入可任意命名。 |

| 语法 | export { a } / export default b | import { a } from '...' / import b from '...' |

| 静态性 | 编译时确定接口,只能在顶层作用域使用。 | 编译时确定依赖,只能在顶层作用域使用。 |

| 绑定 | 导出的是值的动态只读引用(Live Binding)。如果模块内部的值改变,import 导入的值也会跟着改变。 | 导入的变量是只读的,不能在当前模块中重新赋值。 |

掌握 importexport 是现代 JavaScript 开发的基础。它们不仅让代码更加结构化,也是 Webpack、Rollup 等构建工具进行 Tree Shaking(摇树优化,移除未使用的代码)的技术基础。