什么是JavaScript的模块化?
JavaScript的模块化是指将代码分割成独立的、可重用的模块,每个模块具有自己的功能和作用,可以单独进行开发、测试和维护。不同的目的是提升代码的可维护性、可复用性和可扩展性,同时避免不同模块间的命名冲突和依赖问题。
在早期的JavaScript中,所有的代码都被写在同一个文件中,这样会导致以下问题:
- 全局命名污染:所有的变量和函数都在全局作用域中,可能导致命名冲突。
- 代码复制困难:如果需要在不同的项目或模块中复制相同的功能,需要手动复制代码,维护起来非常困难。
- 代码组织差:代码越来越多,逻辑难以分割,导致可维护性差。
为了解决这些问题,JavaScript社区提供了 CommonJS 和 AMD。
CommonJS :
- 为了在服务器端(Node.js环境)实现自定义,CommonJS标准应运而生。
- 使用require()导入模块,module.exports导出模块,解决了代码组织和模块依赖问题。
- CommonJS的设计主要面向同步加载,适用于服务器端。
AMD :
- AMD(如RequireJS)在浏览器环境中解决了优先问题。
- 它支持异步加载模块,避免了浏览器中模块过多导致的性能问题。
- AMD允许第四个加载模块,并且不要求模块按照顺序加载,这在浏览器中尤其有用。
后面官方终于开始做为,在ECMAScript 6(ES6)标准中,JavaScript语言初步支持了标准,提供了import
和export
关键字。
ES模块化:
- 静态分析:模块的依赖关系可以在编译时解析,优化性能。
- 支持异步加载:通过动态
import()
可以辅助加载模块。 - 模块作用域:每个模块都有自己的作用域,避免了全局作用域污染。
ES模块使用方法
1. 具名导出
导出变量、函数、类
// math.js// 导出变量
export const PI = 3.14159;// 导出函数
export function add(a, b) {return a + b;
}// 导出类
export class Calculator {multiply(a, b) {return a * b;}
}
导入变量、函数、类
// main.js
import { PI, add, Calculator } from './math.js';console.log(PI); // 3.14159
console.log(add(2, 3)); // 5const calc = new Calculator();
console.log(calc.multiply(4, 5)); // 20
2. 默认导出 export default
默认导出时,导入的名称可以自定义。
导出
// logger.js
export default function logMessage(message) {console.log("Log:", message);
}
导入
// main.js
import log from './logger.js';log("Hello, World!"); // Log: Hello, World!
3. 组合导出(命名空间)
导出
// utils.js
const name = "JavaScript";
function greet() {return "Hello, " + name;
}
export { name, greet };
导入
// main.js
import * as utils from './utils.js';console.log(utils.name); // JavaScript
console.log(utils.greet()); // Hello, JavaScript// 代码说明
// import * as utils from './utils.js' 代码表示如下:
// *:表示导入该模块中所有导出的内容。
// as:表示将所有导入的内容命名为一个单一的对象(即命名空间),这个对象的名字是 utils。
// 你可以通过 utils 对象访问 utils.js 中导出的所有内容。
4. 重新导出
可以从一个模块导入后重新导出,使其变成一个中转模块。
导出
// math.js
// 导出变量
export const PI = 3.14159;// 导出函数
export function add(a, b) {return a + b;
}// 导出类
export class Calculator {multiply(a, b) {return a * b;}
}
重新导出
// mathOperations.js
export { PI, add, Calculator } from './math.js';
导入
// main.js
import { PI, add } from './mathOperations.js';console.log(PI); // 3.14159
console.log(add(10, 20)); // 30
5. 动态导入 import()
import()
动态导入允许在运行时动态加载模块,适用于按需加载或懒加载场景。结合await
进行异步模块加载。
导出
// math.js
export function add(a, b) {return a + b;
}
导入
// main.js
async function loadMathModule() {const math = await import('./math.js');console.log(math.add(5, 10)); // 15
}
loadMathModule();
6. 浏览器导入
在 HTML 中使用 <script type="module">。代码不会污染全局作用域。浏览器支持 import,不需要 Webpack 等打包工具。
导出
// math.js
export function add(a, b) {return a + b;
}
导入
<!-- index.html -->
<script type="module">import { add } from './math.js';console.log(add(3, 7)); // 10
</script>
7. 在 Node.js 中使用 ES 模块
在 package.json
中添加
{"type": "module"
}
然后可以在 .mjs
文件中使用 ES 模块语法
// server.mjs
import { createServer } from 'http';const server = createServer((req, res) => {res.end("Hello, ES Modules in Node.js!");
});server.listen(3000, () => {console.log("Server running on http://localhost:3000");
});
什么是 .mjs 文件?
上面提到了 .mjs,那么什么是 .mjs 文件?
.mjs 是 JavaScript 模块文件 的扩展名,表示该文件使用了 ES6 模块化(即 ECMAScript 2015 模块)语法。与传统的 .js 文件不同,.mjs 文件明确告知 Node.js 或其他 JavaScript 环境该文件使用的是 ES 模块规范。
为什么使用
.mjs
?
区分 CommonJS 和 ES6 模块:在 Node.js 中,默认的模块系统是 CommonJS,通过
require()
和module.exports
来加载和导出模块。而 ES6 模块 使用import
和export
语法。为了避免混淆,Node.js 引入了.mjs
扩展名来明确区分 ES6 模块文件和传统的 CommonJS 文件。Node.js 与 ES6 模块的兼容性:虽然 ES6 模块是 JavaScript 标准的一部分,但在 Node.js 中直到最近才开始原生支持。为了支持 ES6 模块,Node.js 在 版本 12 及更高版本中引入了
.mjs
文件扩展名。当你在 Node.js 中使用 ES6 模块时,使用.mjs
扩展名可以让 Node.js 知道该文件使用 ES6 模块化语法。
type
字段:从 Node.js 14 开始,你还可以在package.json
文件中设置type
字段来指定整个项目是否使用模块化语法:
- 如果将
type
设置为"module"
,则可以在项目中使用.js
扩展名来作为 ES6 模块,免去.mjs
扩展名的要求。- 如果不设置或将
type
设置为"commonjs"
,则.js
文件将继续被视为 CommonJS 模块。