写在前面
在ES6之前,JavaScript没有原生的模块系统。传统上,我们使用<script>
标签来加载JavaScript文件。这些文件会被顺序执行,最后一个文件的内容会覆盖前面文件的同名变量和函数。
传统方法
html
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Traditional Script Loading</title>
</head>
<body>
<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="main.js"></script>
</body>
</html>
javascript
// module1.js
var module1 = {
message: 'Hello from module 1'
};
// module2.js
var module2 = {
message: 'Hello from module 2'
};
// main.js
console.log(module1.message); // Hello from module 1
console.log(module2.message); // Hello from module 2
加载规则
- 顺序执行:脚本按照在HTML文件中出现的顺序执行。
- 全局作用域:所有变量和函数都在全局作用域中定义,可能会导致命名冲突。
- 异步加载 :可以使用
async
属性来异步加载脚本,但这可能会导致依赖关系混乱。
ES6模块与CommonJS模块的差异
ES6模块和CommonJS模块都是用于解决JavaScript模块化问题的系统,但它们有以下几个主要区别:
- 语法 :ES6模块使用
import
和export
关键字,而CommonJS模块使用require
和module.exports
。 - 静态分析:ES6模块在编译时就能确定依赖关系,而CommonJS模块在运行时动态加载。
- 默认导出:ES6模块支持默认导出,CommonJS模块不支持。
- 循环依赖:ES6模块可以处理循环依赖,CommonJS模块则不能。
Node.js的模块加载方法
Node.js使用CommonJS模块系统。可以使用require
函数来加载模块,并使用module.exports
或exports
对象来导出模块。
javascript
// math.js
exports.add = function(a, b) {
return a + b;
};
// main.js
const math = require('./math');
console.log(math.add(2, 3)); // 5
循环加载
循环加载是指模块A依赖模块B,而模块B又依赖模块A的情况。ES6模块可以处理循环依赖,但需要注意以下几点:
- 只导出已经定义的变量:在循环依赖中,模块可能还没有完全初始化。因此,应该只导出已经定义的变量。
- 避免在模块顶部使用导入的变量:如果在模块顶部使用导入的变量,可能会导致未定义的错误。
以下是一个循环加载的例子:
javascript
// moduleA.js
export function foo() {
console.log('foo');
}
// moduleB.js
import { foo } from './moduleA';
export function bar() {
foo();
console.log('bar');
}
// moduleA.js (继续)
import { bar } from './moduleB';
export function baz() {
bar();
console.log('baz');
}
// main.js
import { baz } from './moduleA';
baz(); // bar -> foo -> baz
在这个例子中,moduleA
和moduleB
相互依赖。我们需要小心地设计模块,以避免循环依赖带来的问题。