**【背景介绍】想象一下,编写 JavaScript 代码就像是为一部大规模的电影编排剧本:每个演员(每一个功能模块)** 都有自己的台词和角色,但整个电影要流畅地进行,演员之间必须协同工作。问题来了,如何让这些角色各司其职,同时又能无缝衔接?答案就是:模块化。
# 传统的模块化:剧本堆积如山
在 ES6 之前,我们在 JavaScript 中采用的模块化机制可以用 “堆积如山” 来形容。常见的模块化方案比如 CommonJS 和 AMD,它们虽然能完成任务,但并不完美。尤其是对于现代浏览器的支持上,这些方案的灵活性和可维护性就显得有点力不从心。
而 **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 模块,你可以选择以下两种方式之一:
- 使用
.js扩展名; - 在 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 代码时,记得给每个模块找一个恰当的角色,让它们协同合作,共同编织出一部完美的 “剧本”。
