JS `import` 与 `export`:标准的模块导入导出语法

嘿,各位未来的JS大师们,欢迎来到今天的“模块化生存指南”讲座!

今天咱们要聊聊JS世界里那对形影不离、相爱相杀(误)的好基友:importexport。 它们就像乐高积木,让你把代码拆成小块,然后又巧妙地拼起来,搭建出复杂的应用程序。 不用担心,咱们不搞那些晦涩难懂的学院派理论,争取用最接地气的方式,让你彻底掌握它们!

一、 模块化:告别意大利面条式代码

想象一下,你写了一个几千行的JS文件,所有变量、函数都挤在一起,简直就是一团意大利面条。改个bug,可能牵一发而动全身,整个页面都崩了。这就是没有模块化的噩梦!

模块化就是把代码分割成独立、可重用的模块。每个模块都有自己的作用域,不会污染全局变量。 这就好比你把房间里的东西分门别类地放在不同的抽屉里,找起来方便,也不容易弄丢。

模块化的好处:

  • 代码复用: 一个模块可以在多个地方使用,减少重复代码。
  • 可维护性: 模块之间相互独立,修改一个模块不会影响其他模块。
  • 可读性: 代码结构更清晰,易于理解和维护。
  • 命名冲突避免: 每个模块都有自己的作用域,避免变量名冲突。

二、 export:把你的宝贝亮出来

export 的作用就是把模块中的变量、函数、类等“导出”出去,让其他模块可以使用。 我们可以把它想象成一个展示橱窗,你把想让别人看到的东西摆在橱窗里。

1. 命名导出 (Named Exports):

这是最常用的导出方式,你可以给每个导出的东西起个名字。

// math.js

export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

export class Circle {
  constructor(radius) {
    this.radius = radius;
  }

  area() {
    return PI * this.radius * this.radius;
  }
}

在这个例子中,我们导出了常量 PI,函数 add 和类 Circle

特点:

  • 可以导出多个变量、函数或类。
  • 必须指定导出的名字。
  • 导入时必须使用相同的名字。

2. 默认导出 (Default Exports):

每个模块只能有一个默认导出。 我们可以把它想象成你的“镇店之宝”,最能代表这个模块的东西。

// logger.js

function log(message) {
  console.log(`[LOG]: ${message}`);
}

export default log;

在这个例子中,我们把函数 log 作为默认导出。

特点:

  • 每个模块只能有一个默认导出。
  • 导入时可以随意命名。
  • 通常用于导出模块的主要功能。

3. 重命名导出 (Export with Aliases):

有时候你觉得原来的名字不够好听,或者和其他模块的名字冲突了,可以用 as 关键字重命名导出的东西。

// utils.js

function reallyLongFunctionName() {
  // ...
}

export { reallyLongFunctionName as shortName };

这样,在其他模块中就可以用 shortName 来使用 reallyLongFunctionName 了。

4. 导出列表 (Export List):

如果你想一次性导出多个东西,可以用导出列表。

// helpers.js

const helper1 = () => {};
const helper2 = () => {};
const helper3 = () => {};

export { helper1, helper2, helper3 };

三、 import:把别人的宝贝拿过来

import 的作用就是把其他模块导出的东西“导入”到当前模块中使用。 我们可以把它想象成从展示橱窗里拿东西,放到自己的房间里。

1. 命名导入 (Named Imports):

导入命名导出的东西,必须使用相同的名字,并且用花括号 {} 包裹。

// app.js

import { PI, add, Circle } from './math.js';

console.log(PI); // 3.14159
console.log(add(2, 3)); // 5

const myCircle = new Circle(5);
console.log(myCircle.area()); // 78.53975

2. 默认导入 (Default Imports):

导入默认导出的东西,可以随意命名,不需要花括号。

// main.js

import logger from './logger.js';

logger('Hello, world!'); // [LOG]: Hello, world!

3. 重命名导入 (Import with Aliases):

如果你觉得导入的名字不好听,或者和其他变量名冲突了,可以用 as 关键字重命名导入的东西。

// main.js

import { shortName as usefulFunction } from './utils.js';

usefulFunction();

4. 导入整个模块 (Import as Namespace):

如果你想一次性导入整个模块,可以用 * as 语法。 这会把模块的所有导出内容放到一个对象里,你可以通过点运算符访问它们。

// main.js

import * as MathUtils from './math.js';

console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.add(2, 3)); // 5

5. 动态导入 (Dynamic Imports):

import() 函数允许你在运行时动态地加载模块。 这是一个异步操作,返回一个 Promise。

// index.js

async function loadModule() {
  const module = await import('./my-module.js');
  module.default(); // 调用模块的默认导出
}

loadModule();

动态导入的优点:

  • 按需加载: 只在需要时才加载模块,提高页面加载速度。
  • 条件加载: 可以根据条件加载不同的模块。
  • 代码分割: 可以把应用程序分割成更小的块,减少初始加载量。

四、 exportimport 的组合拳

exportimport 经常一起使用,构建模块之间的依赖关系。

1. 重新导出 (Re-exporting):

有时候你需要把一个模块导出的东西,再从另一个模块导出出去。 比如,你有一个 index.js 文件,它把多个模块的功能整合在一起,对外提供统一的接口。

// index.js

export { PI, add, Circle } from './math.js';
export { default as logger } from './logger.js';

这样,其他模块就可以直接从 index.js 中导入 PIaddCirclelogger 了,而不用分别导入 math.jslogger.js

2. 混合导出 (Mixed Exports):

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

// my-module.js

export const VERSION = '1.0.0';

function doSomething() {
  // ...
}

export default doSomething;
// app.js

import doSomething, { VERSION } from './my-module.js';

doSomething();
console.log(VERSION); // 1.0.0

五、 模块化的常见问题和注意事项

  • 循环依赖: 模块 A 依赖模块 B,模块 B 又依赖模块 A。 这会导致程序崩溃或死循环。 尽量避免循环依赖,或者使用动态导入解决。
  • 命名冲突: 不同的模块可能导出相同的名字。 使用重命名导入或导出解决。
  • 模块加载器: 在浏览器中使用模块化,需要使用模块加载器,比如 Webpack、Parcel 或 Rollup。 它们会把模块打包成浏览器可以识别的文件。
  • type="module" 在 HTML 中使用 ES 模块,需要在 <script> 标签中添加 type="module" 属性。
<script type="module" src="app.js"></script>

六、 代码示例:一个完整的模块化示例

为了更好地理解 importexport,我们来看一个完整的示例。

目录结构:

my-app/
├── index.html
├── app.js
├── utils/
│   ├── math.js
│   └── string.js

index.html:

<!DOCTYPE html>
<html>
<head>
  <title>模块化示例</title>
</head>
<body>
  <h1>Hello, Modules!</h1>
  <script type="module" src="app.js"></script>
</body>
</html>

utils/math.js:

export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

utils/string.js:

export function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

export function reverse(str) {
  return str.split('').reverse().join('');
}

app.js:

import { add, subtract } from './utils/math.js';
import { capitalize, reverse } from './utils/string.js';

const sum = add(5, 3);
const difference = subtract(10, 4);

const message = 'hello world';
const capitalizedMessage = capitalize(message);
const reversedMessage = reverse(message);

console.log(`Sum: ${sum}`); // Sum: 8
console.log(`Difference: ${difference}`); // Difference: 6
console.log(`Original message: ${message}`); // Original message: hello world
console.log(`Capitalized message: ${capitalizedMessage}`); // Capitalized message: Hello world
console.log(`Reversed message: ${reversedMessage}`); // Reversed message: dlrow olleh

在这个示例中,我们把数学运算和字符串处理的功能分别放在 math.jsstring.js 两个模块中。 然后,在 app.js 中导入这些模块,并使用它们的功能。

总结:

特性 export import
作用 将模块中的变量、函数、类等导出,供其他模块使用 将其他模块导出的变量、函数、类等导入到当前模块中使用
类型 命名导出、默认导出、重命名导出、导出列表 命名导入、默认导入、重命名导入、导入整个模块、动态导入
语法 export const PI = 3.14; export default function() {} export { function1 as alias } import { PI } from './math.js'; import myFunc from './my-module.js'; import { function1 as alias } from './utils.js'; import * as utils from './utils.js'; import('./dynamic.js')
数量 一个模块可以有多个命名导出,但只能有一个默认导出 可以导入多个命名导出或一个默认导出
使用场景 定义模块的公共接口,暴露模块的功能给其他模块 使用其他模块提供的功能,构建应用程序
注意事项 尽量避免循环依赖,选择合适的导出方式,使用模块加载器 避免命名冲突,按需导入,使用动态导入优化加载性能

七、 最后的彩蛋:拥抱模块化,成为JS大师!

恭喜你,已经成功解锁了JS模块化的秘密! 掌握 importexport,你就能写出更清晰、更易维护、更可复用的代码。 记住,模块化是现代JS开发的基石,也是成为一名优秀JS工程师的必备技能。

下次再遇到复杂的项目,不要再害怕了,把代码拆成小块,用 importexport 搭建你的乐高王国吧! 祝你编程愉快,早日成为JS大师!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注