模块化的开发方式使得代码更加灵活,提高代码复用率,方便进行代码的管理。通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数,而不会对全局对象造成污染。
目前流行的模块化主要有 AMD,CMD,CommonJS,ES6 模块这几种。
AMD#
AMD 是 Requirejs 在推广过程中对模块定义的规范化产出。
// 模块的定义
// 不依赖其他模块
// math.js
define(function () {
function add(a, b) {
return a + b;
}
// 通过return提供接口
return {
add: add,
};
});
// 依赖其他的模块
// foo.js
define(["math"], function (math) {
function fn(a, b) {
return math.add(a, b);
}
// 通过return提供接口
return {
fn: fn,
};
});
// 模块的引入
// bar.js
require(["foo"], function (foo) {
function bar(a, b) {
return foo.fn(a, b);
}
console.log(bar(1, 2)); // 3
});<!--在html中引入,需要导入requireJs库-->
<script
data-main="./bar"
src="https://cdn.bootcss.com/require.js/2.3.5/require.js"
></script>AMD 模块规范主要用于浏览器端,因为需要从服务器请求模块,同步的方法影响用户体验,只能是异步加载,每个模块如果需要依赖前置模块,只有等前置模块加载完成后,才去执行回调函数。但是不影响程序后面语句的执行。
CMD#
CMD 是sea.js在推广过程中对模块定义的规范化中产出
// 模块的定义和引入
// math.js
define(function (require, exports, module) {
// 通过require引入依赖
const module = require("module");
const add = function (a, b) {
return a + b;
};
// 通过exports对外提供接口
exports.add = add;
// 或者通过module.export提供整个接口
module.export = {};
});
// 模块的引入
// foo.js
define(function (require, exports, module) {
const add = require("math").add;
console.log(add(1, 2)); // 3
});CMD 没有把依赖写在 define 里,而是写在回调函数中,当需要依赖时,才通过 require 去引入。回调函数包含 3 个参数,分别是 require,exports,module。其中 require 是唯一可以引入依赖的,另外 2 个都是对外提供接口的。
AMD 和 CMD 的区别:
- AMD 推崇依赖前置,提前执行。把依赖的引入放在 define 中,需要先加载完依赖,再执行回调函数。
- 而 CMD 推崇就近依赖,延迟执行。把依赖的引入放在回调的 factory 函数中,当要用到依赖时才会就近加载。
CommonJS#
CommonJS 是 Node 服务器的模块规范化。
// 模块的定义
// math.js
const add = function (a, b) {
return a + b;
};
const number = 1;
module.exports = {
fn: add,
num: number,
};
// 模块的引入
const math = require("./math");
console.log(math.fn(1, 2)); // 3
console.log(math.num); // 1CommonJS 模块化主要是用于 Node 服务器端或者是 Node 运行环境中。因为在服务器端,依赖都保存在本地硬盘中,所以读取速度非常快,可以同步加载。
在第一次用 require 引入模块时,会加载并执行整个脚本,然后在内存中生成脚本的 exports 对象。后面再调用这个模块都是从这个 exports 对象中去取值,即使后来模块修改了值,也不会更改内存中对象的值,除非手动清除掉缓存。
现在写前端代码一般都需要借助构建工具或者 MVVM 框架,这些工具和框架都是在 Node 环境下运行的,所以 CommonJS 规范被广泛的使用。可以说使用 Node 环境开发,就会用到 CommonJS 模块。
ES6#
ES6 实现了模块功能,有属于自己的模块规范。
// 模块的定义
// 输出变量
export const number = 1;
// 输出函数
export function add(a, b) {
return a + b;
}
// 也可以集中输出
const name = "hale";
function foo() {
console.log("hale");
}
export { name, foo };
// 默认输出
export default {
add(a, b) {
return a + b;
},
};// 模块的引入
// 引入fs库(第三方库)
import fs from "fs";
// 引入模块的默认输出
import Add from "./add.js";
// 引用模块输出的某个函数和变量
import { add, number } from "./add.js";
// 引入整个模块, 用as作为别名
import * as math from "./add.js";
Add(1, 2); // 3
add(1, 2); // 3
math.add(1, 2); // 3
math.number; // 1
// 也可以直接引入,这样引入就直接加载这个模块
import "index.css";ES6 模块化功能主要通过import引入和export输出来实现的,另外 ES6 模块是动态取值的,不会缓存运行的结果。
ES6 模块化功能不仅支持客户端,还支持服务器(node 服务器)。
不过在客户端加载需要注意一些事项,比如自动采用严格模式,js 后缀名不能省略,代码是在模块作用域下运行的等等,另外在服务器上上加载比较麻烦,因为 Node 服务器有自己的 CommonJS 模块化规范,和 ES6 的有些不兼容,
目前的解决方案是,将两者分开,ES6 模块和 CommonJS 采用各自的加载方案。
ES6 模块和 CommonJS 模块的差异:
-
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的值是值的引用。
- CommonJS 模块输出的是一个值的拷贝,一旦输出值后,模块的变化将不会影响到这个值。
- ES6 模块输出的值是值的引用。当脚本运行时,才通过 import 生成一个引用去模块中取值,如果模块的原始值发现变化了,import 取得的值也会发生变化。
-
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- CommonJS 模块是加载整个模块,然后生成一个对象 exports,再从这个对象上面读取方法,这种加载称为“运行时加载。
- ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,再通过 import 命令输入。import 可以指定加载某个输出的方法,其他的方法不加载,这种加载称为“编译时加载”或者“静态加载”。