Javascript
前端模块化。其中就有 CJS
、AMD
、CMD
、UMD
和 ESM
。
CJS
CJS
是CommonJS
的缩写。主要用于后端,在nodejs中,node应用是由模块组成,采用的Commonjs模块规范。
语法
暴露模块 module.exports=value
或exports=module.export exports.xxx=value
引入模块 require(xxx)
(如果引入为第三方模块,xxx为模块名;如为自定义模块,xxx为模块的文件路径)
const doSomething = require('./doSomething.js');
module.exports = function doSomething(n) {
// do something
}
特点
- 每一个文件就是一个模块,拥有自己独立的作用域,变量,以及方法等,对其他的模块都不可见。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
- CJS模范规定,每个模块内部module变量为当前模块,这个变量其实是一个对象,他的exports属性(module.exports)是对外的接口(加载某个模块其实是加载改模块的module.exports属性)
- CJS加载机制是输入的是输出的值的拷贝,输出一个值,在脚本执行完后才生成,模块内部变化影响不到这个值(理解:require是浅拷贝,也就是说你可以修改对象第二层的属性并影响原数据)
// module.js
let counts = 1;
function sayHello() { alert(`"hello , ${counts}`) }
setTimeout(() => { counts += 2 }, 3000);
module.exports = {counts, sayHello};
//index.js
const { counts, sayHello } = require('./module.js');
// 注意此处的代码结果
const click = () => { console.log('counts: ', counts)
// 每次点击事件,打印的 counts 始终为 1
sayHello(); // 1 ==》 3秒钟后 =>> 3 }
... <!-- 此处counts始终是 1 -->
<p>import {counts}</p>
<Button type="primary" onClick={click}>require </Button>
AMD
CJS
加载模块是同步的,只有加载完成才执行后续的操作。AMD规范是非同步加载模块,允许制定回调函数。Node.js主要是服务器编程,模块文件一般在本地磁盘,加载比较快,不要考虑同步回调的方法,所以CJS适用,但浏览器环境要从服务端加载模块,就需要非同步模式,因此(在没ES6前)浏览器一般使用AMD规范
语法
- 规范代表库:require.js
暴露模块:无依赖的模块 define(function(){ return xxx})
有依赖的模块define(['module1','module2'], function(m1,m2){ return xxx})
引入模块 require(['module1','module2'], function(m1,m2){ //使用m1,m2})
特点
- AMD依赖定义方法清晰,不污染全局环境,明确依赖关系
- 可用于浏览器环境,且允许非同步加载,也可根据需要动态加载
CMD
CMD与AMD很类似,不同点在于:AMD推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js推广过程中产生的。
代码示例
- 规范代表库:sea.js SeaJS是一个JavaScript模块加载框架,可以实现JavaScript的模块化开发及加载机制。
define(function(require, exports, module) {
var a = require('./a'); //在需要时申明
a.doSomething();
if (false) {
var b = require('./b');
b.doSomething();
}
});
/** sea.js **/
// 定义模块 math.js
define(function(require, exports, module) {
var $ = require('jquery.js');
var add = function(a,b){ return a+b; }
exports.add = add;
});
// 加载模块
seajs.use(['math.js'], function(math){
var sum = math.add(1+2);
});
UMD
UMD规范只是一种通用的写法,是在AMD和CJS两个流行而不统一的规范情况下,才催生出UMD来统一规范的,UMD前后端均可通用。
- 规范代表库:sea.js
SeaJS是一个JavaScript模块加载框架,可以实现JavaScript的模块化开发及加载机制。
代码示例
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery', 'underscore'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS之类的
module.exports = factory(require('jquery'), require('underscore'));
} else {
// 浏览器全局变量(root 即 window)
root.returnExports = factory(root.jQuery, root._);
}
}(this, function ($, _) {
// 属性
var PI = Math.PI;
// 方法
function a() { }; // 私有方法,因为它没被返回
function b() { return a() }; // 公共方法,因为被返回了
function c(x, y) { return x + y }; // 公共方法,因为被返回了
// 暴露公共方法
return {
ip: PI,
b: b,
c: c
}
}));
ESM
ESM
代表 ES
模块。这是 Javascript
提出的实现一个标准模块系统的方案。
代码示例
import {foo, bar} from './myLib';
...
export default function() {
// your Function
};
export const function1() { ...};
export const function2() {...};
特点
- ES6模块化,是尽量静态化,编译时就能确定模块的依赖关系,以及输入输出变量。CJS和AMD都是运行时确定。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
- ES6 模块不是对象,而是通过
export
命令显式指定输出的代码,再通过import
命令输入。ES6对外接口是一种静态定义,代码解析就生成 -
ES6
的静态模块结构,可以进行 Tree Shaking。允许像Rollup
这样的打包器,删除不必要的代码,减少代码包可以获得更快的加载 - 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
- 模块脚本自动采用严格模式,不管有没有声明
use strict
。模块之中,顶层的this
关键字返回undefined
,而不是指向window
。也就是说,在模块顶层使用this
关键字,是无意义的。 - 模块之中,可以使用
import
命令加载其他模块(.js
后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用export
命令输出对外接口。 - 同一个模块如果加载多次,将只执行一次。
总结+补充
1.CJS主要用于服务器端,加载模块同步,所以不适合浏览器,因此有了AMD,CMD方案
2.AMD在浏览器异步加载模块,且并行加载多个模块,不过AMD开发成本高阅读代码比较困难,模块定义方式不流
3.CMD,AMD相似,都用于浏览器,CMD依赖就近,延迟执行,可以在使用前才声明;而AMD推崇依赖前置,提前执行,在最前声明的时候,初始化了所有的声明模块
4.ESM简单语法,异步特性和和可摇树性
ESM和CJS区别
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- CommonJS 模块的
require()
是同步加载模块,ES6 模块的import
命令是异步加载,有一个独立的模块依赖的解析阶段。
第一个差异是因为CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令
import
,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import
有点像 Unix 系统的“符号连接”,原始值变了,import
加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
第二个差异是因为 CommonJS 加载的是一个对象(即
module.exports
属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。