[TOC]
- 将一些零散代码封装成一个有用的单元(encapsulate)
- 导出模块的接口API(exports)
- 方便友好引用其它模块(dependency)
CommonJS
在服务器端运行的 NodeJS,遵循了一个被称为 CommonJS 的规范,其中包含了对于模块的标准化。
以服务器端为第一(server-first)的原则,同步加载模块。模块格式是无需包装的(unwrapped modules)且贴近于ES.next / Harmony 的,但仅支持对象类型(objects)模块。
CommonJS module 的基本要求如下:
- 标示符 require 为一个函数,require(string)
- require 方法返回指定的模块 API;
- 如果存在依赖的其它模块,那么依次加载;
- require 不能返回,则抛出异常;
- 仅能使用标示符 exports 导出 API ```javascript // 导出模块 module.exports = {} exports.xxx = ‘xxx’ // 需要注意:exports = {xxx: ‘xxx’} 是错误的写法 //把引用从 module.export 改为了另一个 Object, 导致无法导出模块, // 可以写成 exports = module.exports = {xxx: ‘’}
// 引用模块 require var m1 = require(‘./m1.js’)
// require 可以使用表达式 var m1Url = ‘./m1.js’; var m1 = require(m1Url);
// 甚至做一些字符串拼接: var m1 = require(‘./m’ + ‘1.js’);
<a name="VfM7m"></a>
### node 模块解析
Node.js 会根据 require 的是相对路径还是非相对路径做出不同的行为。
<a name="MQSTw"></a>
#### 相对路径
例如,假设有一个文件路径为 /root/src/moduleA.js,包含了一个导入var x = require("./moduleB"); Node.js以下面的顺序解析这个导入:
1. 检查 /root/src/moduleB.js 文件是否存在。
1. 检查 /root/src/moduleB 目录是否包含一个package.json文件,且 package.json 文件指定了一个"main"模块。 在我们的例子里,如果Node.js发现文件 /root/src/moduleB/package.json 包含了{ "main": "lib/mainModule.js" },那么 Node.js 会引用 /root/src/moduleB/lib/mainModule.js。
1. 检查 /root/src/moduleB 目录是否包含一个 index.js 文件。 这个文件会被隐式地当作那个文件夹下的"main"模块。
<a name="dIjTo"></a>
#### 绝对路径
Node会在一个特殊的文件夹 node_modules 里查找你的模块。 node_modules 可能与当前文件在同一级目录下,或者在上层目录里。 Node会向上级目录遍历,查找每个 node_modules,直到它找到要加载的模块。
假设 /root/src/moduleA.js 里使用的是非相对路径导入 var x = require("moduleB");。 Node 则会以下面的顺序去解析 moduleB,直到有一个匹配上。
1. /root/src/node_modules/moduleB.js
1. /root/src/node_modules/moduleB/package.json (如果指定了"main"属性)
1. /root/src/node_modules/moduleB/index.js
4. /root/node_modules/moduleB.js
4. /root/node_modules/moduleB/package.json (如果指定了"main"属性)
4. /root/node_modules/moduleB/index.js
7. /node_modules/moduleB.js
7. /node_modules/moduleB/package.json (如果指定了"main"属性)
7. /node_modules/moduleB/index.js
注意Node.js在步骤(4)和(7)会向上跳一级目录。
<a name="FFVmO"></a>
## AMD
由于 CommonJS 是同步加载模块,如果用在客户端,一旦模块加载很慢,需要等待模块加载完才能执行下面的内容,造成卡顿,对显示和使用是很不友好的。<br />AMD 以浏览器为第一(browser-first)的原则,**异步**加载模块。它的模块支持对象(objects)、函数(functions)、构造器(constructors)、字符串(strings)、JSON 等各种类型的模块。因此在浏览器中非常灵活。<br />AMD module的基本要求如下:<br />需要引入 requirejs 这类库(定义了 define)。<br />写模块API:define 。
```javascript
define([id, ] [dependencies, ] factory);
- id: 模块标识[可以省略]; [id 遵循 CommonJS Module Identifiers]
- dependencies: 所依赖的模块[可以省略]; [dependencies 元素的顺序和factory参数一一对应]
- factory: 模块的实现,或者一个 JavaScript 对象。
- dependencies 的模块都加载完,再执行 factory。 ```javascript // math.js define(function () { var add = function (a, b) { return a + b; } return { add: add } });
// test.js var requirejs = require(“requirejs”); //引入requirejs模块
requirejs([‘math’],function(math) { console.log(math) console.log(math.add(1, 2)); });
<a name="77Q9S"></a>
### CMD
> CMD (Common Module Definition), 是seajs推崇的规范,CMD则是依赖就近,用的时候再require。
与 AMD 的主要区别在于模块定义时对依赖的处理不同。
```javascript
// 假设 m1 模块会 console.log('m1'),
// AMD: m1 -> start
define(['m1'], function (m1) {
console.log('start');
return {
print: function() {
console.log(m1.name);
}
};
});
// CMD: start -> m1
define(function (require, exports, module) {
console.log('start');
var m1 = require('./m1.js');
module.exports = {
print: function() {
console.log(m1.name);
}
};
});
UMD——AMD 和CommonJS的糅合
UMD 的实现很简单,
先判断是否支持 NodeJS 模块格式(exports 是否存在),存在则使用 NodeJS 模块格式。
再判断是否支持 AMD(define 是否存在),存在则使用 AMD 方式加载模块。
前两个都不存在,则将模块公开的全局(window 或 global)。
ES modules( es )
使用 import 载入模块,使用 module.something 和 export 创建模块。
const a = 1;
export { a };
// 接口名与模块内部变量之间,建立了一一对应的关系
export const a = 1, b = 2;
// 接口名与模块内部变量之间,建立了一一对应的关系
export const a = 1;
export const b = 2;
// 或者用 as 来命名
const a = 1;
export { a as outA };
const a = 1;
const b = 2;
export { a as outA, b as outB };
// 默认导出
const a = 1;
export default a;
const a = 1;
export default { a };
export default function() {}; // 可以导出一个函数
export default class(){}; // 也可以出一个类
const a = 1;
export defalut a;
// 等价于
export { a as default }
// 注意:若下面的导出语句使用 b.js,b.js 中是不能使用 someVariable 变量做什么操作的
export { someVariable } from './a';
// 1. 这里的a得和被加载的模块输出的接口名对应
import { a } from './module'
// 2. 使用 as 换名
import { a as myA } from './module'
// 3. 若是只想要运行被加载的模块可以这样写,但是即使加载2次也只是运行一次
import './module'
// 4. 整体加载
import * as module from './module'
// 5. default接口和具名接口
import module, { a } from './module'
// ES2020 引入了模块动态导入
const modName = 'module';
import(`./${modName}`).then((module) => {
const mod = module.default || module;
});
为什么要输出不同形式的模块的包,比如 cjs、es 和 umd?
以 vue 为例,
- umd 是可以直接使用
