模块化的发展历程:
按照诞生时间的先后顺序
- CommonJs,2009年1月
- AMD
Asynchronous Module Definition
, 2011年2月 - CMD
Common Module Definition
, 2011年4月后 - UMD
Universal Module Definition
, 2014年9月 - ESM
EcmaScript Module
, 2016年5月
一、CommonJs 模块规范
第一阶段: Modules/1.0, 产物 browserify
第二阶段: Modules/Async, 诞生了 Modules/AsynchronousDefinition
规范
第三阶段: Modules/2.0,基于 CommonJS 规范的实现:BravoJS
- 运行时加载,一个文件就是一个模块
- 更适合服务端,node.js 采用了这个规范
- 同步加载模块,不支持异步
- 使用
require/exports
关键字
二、AMD 和 CMD
AMD
是 RequireJS 在推广过程中对模块定义的规范化产出。
- 一种基于模块的异步加载的js代码机制
- 通过延迟和按需加载来解决各个模块之间的依赖关系,
依赖前置、提前执行
define
关键字用于定义模块,require
关键字用来引用模块
CMD
是阿里的玉伯(王保平)在借鉴了CommonJs 和 AMD 方案后写出的 SeaJS 在推广过程中对模块定义的规范化产出。
- 与AMD大体相同,也是异步加载
- 特点是
依赖就近、延迟执行
AMD 和 CMD 的依赖和执行时机的比较
1、AMD:
// a.js
define(function () {
console.log('init a.js');
return {
getFun: function () {
return 'I am a';
}
};
});
// b.js
define(function () {
console.log('init b.js');
return {
getFun: function () {
return 'I am b';
}
};
});
// index.js
define(function (require) {
var a = require('./a');
console.log(a.getFun());
var b = require('./b');
console.log(b.getFun());
});
<script src="https://cdn.bootcss.com/require.js/2.3.6/require.min.js"></script>
<script>
requirejs(['index']);
</script>
// 输出结果如下
// init a.js
// init b.js
// I am a
// I am b
2、CMD:
// a.js
define(function (require, exports, module) {
console.log('init a');
exports.getFun = function () {
return 'I am a';
}
});
// b.js
define(function (require, exports, module) {
console.log('init b');
exports.getFun = function () {
return 'I am b';
}
});
// index.js
define(function(require, exports, module) {
var a = require('./a'); //在需要时申明
console.log(a.getFun());
var b = require('./b'); //在需要时申明
console.log(b.getFun());
});
<script src="https://cdn.bootcss.com/seajs/3.0.3/sea.js"></script>
<script>
seajs.use('./index.js')
</script>
// 输出
// init a
// I am a
// init b
// I am b
三、UMD
- 前后端跨平台的解决方案
- 兼容 AMD 和 CommonJS 的规范,并支持传统的声明全局变量的方式
- 先判断是否支持 AMD(define是否存在),存在则使用 AMD 方式加载模块
- 再判断是否支持 Node.js 模块格式( exports 是否存在),存在则使用 Node.js 模块格式
- 前两个都不存在,则将模块公开到全局( window 或 global )
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery', 'underscore'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(require('jquery'), require('underscore'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.jQuery, root._);
}
}(this, function ($, _) {
// methods
function a(){}; // private because it's not returned (see below)
function b(){}; // public because it's returned
function c(){}; // public because it's returned
// exposed public methods
return {
b: b,
c: c
}
}));
四、ESM
2015年6月,ES6/ES2015 正式通过决议,从语言层面提出的一种模块化标准。
- 编译时加载,模块加载从入口文件开始,最终生成完整的模块实例关系图,过程包含:
- 构建:查找,下载,然后把所有文件解析成 module record。
- 实例化:为模块分配内存空间,依照导入,导出语句把模块指向对应内存地址。
- 运行:把内存空间填充为真实值。
- node V8.5.0+ 对其进行了支持
- 使用
import / export
关键字
导入导出方式
1. 默认导入导出
使用 export default
关键字。
// 文件 a.js
// 导出默认对象
export default {
// 定义 hello 方法,输出欢迎信息
hello(name) {
console.log(`Hello, ${name}!`)
},
// 定义 byebye 方法,输出道别信息
byebye(name) {
console.log(`byebye, ${name}!`)
},
// 定义 userInfo 对象,存储用户信息
userInfo: {
name: 'vavid', // 用户名
age: 18 // 用户年龄
}
}
// 文件 b.js
// 引入 a.js 中默认导出的模块
import defaultModule from './a.js'
// 调用 defaultModule 中定义的 hello() 方法,输出欢迎信息并传入用户姓名
defaultModule.hello(defaultModule.userInfo.name) // Hello,vavid!
// 调用 defaultModule 中定义的 byebye() 方法,输出道别信息并传入用户姓名
defaultModule.byebye(defaultModule.userInfo.name) // byebye,vavid!
2.具名导入导出
具名导出,使用 export
关键字。
// 文件 a.js
// 定义 hello 方法,输出欢迎信息
export function hello(name) {
console.log(`Hello, ${name}!`)
}
// 定义 byebye 方法,输出道别信息
export function byebye(name) {
console.log(`byebye, ${name}!`)
}
// 定义 userInfo 对象,存储用户信息
export const userInfo = {
name: 'vavid', // 用户名
age: 18 // 用户年龄
}
// 文件 b.js
// 引入 a.js 中具名导出的模块
import { byebye, hello, userInfo as user } from './a.js'
// 调用 byebye() 方法,输出道别信息并传入用户姓名
byebye(user.name)
// 调用 hello() 方法,输出欢迎信息并传入用户姓名
hello(user.name)
Tips: 使用 as
关键字还可以修改导入内容的名称。
3.全部导入导出
可以将另一个模块的内容直接全部导出。
导出同时也可以设置默认导出。
// 文件 b.js
// 从 a.js 中导出所有的模块成员
export * from './a.js'
// 导出一个默认模块,对象包含 goal 属性,初始值为 'learn'
export default {
goal: 'learn'
}
使用 import * as xx from 'module'
导入所有内容。
// 文件 c.js
// 导入 b.js 中所有被导出的模块成员,并作为 allValues 对象的属性
import * as allValues from './b.js'
// 在控制台输出 allValues 对象
console.log(allValues)
// 从 allValues 对象中解构出 hello、byebye、default 和 userInfo 模块成员
const { hello, byebye, default: data, userInfo } = allValues
// 调用 hello() 方法,输出欢迎信息并传入用户姓名
hello(userInfo.name)
// 调用 byebye() 方法,输出道别信息并传入用户姓名
byebye(userInfo.name)
// 输出 data 对象的 goal 属性
console.log(data.goal) // learn
4.重新导出
// lib.js
export function hello(name) {
console.log(`Hello, ${name}!`)
}
export default {
filename: 'lib.js',
des: 'lib.js的一些默认导出'
}
// util.js
export function byebye(name) {
console.log(`ByeBye, ${name}!`)
}
export default {
filename: 'util.js',
des: 'util.js的一些默认导出'
}
// index.js
// 从 './lib.js' 中导出 hello 和默认导出并重命名为 libData
export { hello, default as libData } from './lib.js'
// 从 './util.js' 中导出所有命名导出
export * from './util.js'
// 从 './util.js' 中默认导出并重命名为 utilData
export { default as utilData } from './util.js'
此时通过 index.js
统一对外导出。
// usage.js
import { hello, byebye, libData, utilData } from './index.js'
hello(libData.filename) // Hello, lib.js!
byebye(utilData.filename) // ByeBye, util.js!
参考
https://github.com/amdjs/amdjs-api/wiki/AMD
CMD 模块定义规范
Sea.js 与 RequireJS 的异同
https://www.davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/
https://dev.to/iggredible/what-the-heck-are-cjs-amd-umd-and-esm-ikm
编程时间简史