嘿,各位未来的JS大师们,欢迎来到今天的“模块化生存指南”讲座!
今天咱们要聊聊JS世界里那对形影不离、相爱相杀(误)的好基友:import
和 export
。 它们就像乐高积木,让你把代码拆成小块,然后又巧妙地拼起来,搭建出复杂的应用程序。 不用担心,咱们不搞那些晦涩难懂的学院派理论,争取用最接地气的方式,让你彻底掌握它们!
一、 模块化:告别意大利面条式代码
想象一下,你写了一个几千行的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();
动态导入的优点:
- 按需加载: 只在需要时才加载模块,提高页面加载速度。
- 条件加载: 可以根据条件加载不同的模块。
- 代码分割: 可以把应用程序分割成更小的块,减少初始加载量。
四、 export
和 import
的组合拳
export
和 import
经常一起使用,构建模块之间的依赖关系。
1. 重新导出 (Re-exporting):
有时候你需要把一个模块导出的东西,再从另一个模块导出出去。 比如,你有一个 index.js
文件,它把多个模块的功能整合在一起,对外提供统一的接口。
// index.js
export { PI, add, Circle } from './math.js';
export { default as logger } from './logger.js';
这样,其他模块就可以直接从 index.js
中导入 PI
、add
、Circle
和 logger
了,而不用分别导入 math.js
和 logger.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>
六、 代码示例:一个完整的模块化示例
为了更好地理解 import
和 export
,我们来看一个完整的示例。
目录结构:
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.js
和 string.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模块化的秘密! 掌握 import
和 export
,你就能写出更清晰、更易维护、更可复用的代码。 记住,模块化是现代JS开发的基石,也是成为一名优秀JS工程师的必备技能。
下次再遇到复杂的项目,不要再害怕了,把代码拆成小块,用 import
和 export
搭建你的乐高王国吧! 祝你编程愉快,早日成为JS大师!