**【背景介绍】想象一下,编写 JavaScript 代码就像是为一部大规模的电影编排剧本:每个演员(每一个功能模块)** 都有自己的台词和角色,但整个电影要流畅地进行,演员之间必须协同工作。问题来了,如何让这些角色各司其职,同时又能无缝衔接?答案就是:模块化

# 传统的模块化:剧本堆积如山

ES6 之前,我们在 JavaScript 中采用的模块化机制可以用 “堆积如山” 来形容。常见的模块化方案比如 CommonJSAMD,它们虽然能完成任务,但并不完美。尤其是对于现代浏览器的支持上,这些方案的灵活性和可维护性就显得有点力不从心。

而 **ES6 模块化(简称 ESM)** 是现代 JavaScript 的一剂强心针,带来了更加简洁、强大、并且浏览器友好的模块化方案。让我们来深入了解一下它如何改变了我们的代码世界。


# ES6 模块化:每个模块都有自己的职责

在 ES6 中,模块化就像是为每个角色设置了一个单独的剧本,所有的台词和角色关系都井井有条。两个关键字就能完成所有工作: export import

  • export :暴露模块的内容,让外部能够使用这些内容。
  • import :从其他模块中引入内容,模块与模块之间的协作变得无缝且高效。

# 1. 命名导出

命名导出(有些地方叫分别暴露)允许你在一个模块中暴露多个成员,类似于一个剧本有多个角色,每个角色都能有自己的台词。

# 暴露多个成员

想象你有一个 utils.js 文件,里面有多个函数和常量:

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

export function multiply(a, b) {
  return a * b;
}

export const pi = 3.14159;

引入多个成员

在另一个文件(比如 app.js)中,我们可以用 import 引入这些功能,像是演员们从剧本中读出台词:

// app.js
import { add, multiply, pi } from './utils.js';

console.log(add(2, 3));       // 输出: 5
console.log(multiply(2, 3));  // 输出: 6
console.log(pi);              // 输出: 3.14159

重命名导入:用别名呼唤角色

有时候,我们需要给某个角色起个别名。 as 关键字帮助我们做到了这一点:

// app.js
import { add as sum, multiply as prod } from './utils.js';

console.log(sum(2, 3));  // 输出: 5
console.log(prod(2, 3)); // 输出: 6

# 2. 默认导出

如果你的模块中只有一个核心功能,使用默认导出就非常合适。这就像是电影的主角,所有的关注点都集中在他身上。

暴露默认成员

例如,你有一个问候函数 greet.js,它是这个模块的主角:

// greet.js
export default function greet(name) {
  return `Hello, ${name}!`;
}

引入默认成员

在另一个文件中引入时,你不需要花括号,直接调用它:

// app.js
import greet from './greet.js';

console.log(greet('Alice')); // 输出: Hello, Alice!

暴露默认对象

默认导出不仅限于函数,你还可以导出一个对象、类,甚至是其他值。例如,配置对象 config.js

// config.js
const config = {
  apiUrl: 'https://api.example.com',
  apiKey: '12345'
};

export default config;

app.js 中引入它:

// app.js
import config from './config.js';

console.log(config.apiUrl);  // 输出: https://api.example.com

# 3. 再导出(开发中用的较少):一手交钱一手交货

有时你并不需要直接定义一个模块的内容,而是希望将其引入后转交给其他模块。这就像是导演将剧本交给了另一位导演,后者负责把剧本变成实际的演出。

// index.js
export { add, multiply } from './utils.js';

然后,你可以从 index.js 中引入这些内容,index.js 在这里相当于一个中介:

// app.js
import { add, multiply } from './index.js';

console.log(add(2, 3));        // 输出: 5
console.log(multiply(2, 3));   // 输出: 6

# 4. 动态导入:按需加载

动态导入是 ES6 模块化的一个超酷特性。想象一下电影中的惊艳特效或神秘角色,只有在剧情需要时才会出现,这样就避免了不必要的资源浪费。

动态导入通过 import() 来实现,返回一个 Promise ,意味着模块可以异步加载:

// app.js
async function loadUtils() {
  const { add, multiply } = await import('./utils.js');
  console.log(add(2, 3));        // 输出: 5
  console.log(multiply(2, 3));   // 输出: 6
}

loadUtils();

# 5. Node.js 中使用 ES6 模块

虽然 ES6 模块最初是为浏览器设计的,但它已经逐步被 Node.js 接纳。在 Node.js 中,要启用 ES6 模块,你可以选择以下两种方式之一:

  1. 使用 .js 扩展名;
  2. package.json 中设置 "type": "module"
{
  "type": "module"
}

这样,你就可以在 Node.js 中愉快地使用 import 语法了:

// app.js
import greet from './greet.js';

console.log(greet('Alice')); // 输出: Hello, Alice!

# 总结:从 “堆积如山” 到 “井然有序”

ES6 模块化不仅仅是 JavaScript 语法的升级,它改变了我们组织代码的方式。通过 命名导出默认导出动态导入 等强大特性,代码变得更加模块化、清晰易懂、维护起来也更加轻松。无论是浏览器端还是 Node.js,ES6 模块化的优势已经无可忽视。

所以,下次当你编写 JavaScript 代码时,记得给每个模块找一个恰当的角色,让它们协同合作,共同编织出一部完美的 “剧本”。