[TOC]
  1. 将一些零散代码封装成一个有用的单元(encapsulate)
  2. 导出模块的接口API(exports)
  3. 方便友好引用其它模块(dependency)

CommonJS

在服务器端运行的 NodeJS,遵循了一个被称为 CommonJS 的规范,其中包含了对于模块的标准化。
以服务器端为第一(server-first)的原则,同步加载模块。模块格式是无需包装的(unwrapped modules)且贴近于ES.next / Harmony 的,但仅支持对象类型(objects)模块。
CommonJS module 的基本要求如下:

  1. 标示符 require 为一个函数,require(string)
  2. require 方法返回指定的模块 API;
  3. 如果存在依赖的其它模块,那么依次加载;
  4. require 不能返回,则抛出异常;
  5. 仅能使用标示符 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);
  1. id: 模块标识[可以省略]; [id 遵循 CommonJS Module Identifiers]
  2. dependencies: 所依赖的模块[可以省略]; [dependencies 元素的顺序和factory参数一一对应]
  3. factory: 模块的实现,或者一个 JavaScript 对象。
  4. 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 是可以直接使用