前端模块化:CommonJS / AMD / CMD / ES6 Module

142 阅读4分钟

一、模块化开发的理解

模块是实现特定功能的一组属性和方法的封装

二、4 种模块规范

1. CommonJS(Node.js)

这种模块规范方案主要用于服务器端Node.js 实践了该规范。node.js的接口:

  • 输出模块接口:module.exports
  • 加载模块:require

CommonJS 以同步的方式来加载模块。因为在服务器端文件都存储在本地磁盘,所以读取非常快,同步加载没有问题。但在浏览器端的话,模块加载需要浏览器发起请求到服务端,等待返回文件,使用异步加载更合适

2. AMD(require.js)

(多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器)

AMD 规范以异步方式加载模块,即模块的加载不影响它后面的语句执行。将所有依赖这个模块的语句都放到回调函数里,等待加载完成后再执行回调函数require.js 实践了该规范,它的接口:

  • 指定引用路径:require.config()
  • 定义模块:define
  • 加载模块:require

3. CMD(sea.js)、两者区别

CMD 也是异步方式,sea.js 实践了该规范。它和 AMD 相似,区别在于 模块定义时对依赖的处理不同对依赖模块的执行时机的处理不同

  • AMD:

    • 依赖前置:在定义模块时要先声明其依赖的模块;
    • 提前执行:因为 JS 知道该模块依赖的模块是哪些,所以可以提前执行其依赖的模块。
    • 用户体验好,因为模块已经提前执行完。
    • 依赖模块的执行顺序和书写顺序可能不一致,哪个先下载下来、哪个先执行。
  • CMD:

    • 依赖就近:只要依赖的模块在附近就可以了;
    • 延迟执行:加载完某个依赖的模块后,不会立即执行,而是遇到 require 语句时再执行。
    • 性能好,用户需要时才执行。
    • 模块的执行顺序和书写顺序是一致的

ES6之前,主要用的是CommonJS 和 AMD。

三、第4种模块规范:ES6

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性

// CommonJS模块
let { stat, exists, readfile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

运行时加载:上面代码的实质是先整体加载fs模块(即加载fs的所有方法),生成一个对象_fs),然后再从这个对象上面读取 3 个方法。这种加载称为 “运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。

ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。

// ES6模块
import { stat, exists, readFile } from 'fs';

编译时加载:上面代码的实质是从fs模块加载需要的方法,其他方法不加载,即ES6可以在编译时就完成模块加载,效率更高。

1. export

// 写法一
export var m = 1;
// 写法二
var m = 1;
export {m};
// 写法三
var n = 1;
export {n as m};
// 写法一
export function f() {};

// 写法二
function f() {}
export {f};

2. import

import命令具有提升效果,会提升到整个模块的头部,首先执行。import命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行。

import { firstName, lastName, year } from './profile.js';  // 导入和导出名一致

3. 模块整体加载

用星号(*)指定一个对象。

import * as circle from './circle';

console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));

4. export default

export default命令,为模块指定默认输出。其他模块加载该模块时,import命令可以为该匿名函数指定任意名字,不需要大括号。

// export-default.js
export default function () {
  console.log('foo');
}

// import-default.js
import customName from './export-default';
customName(); // 'foo

export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应export default命令。

5. CommonJS 和 ES6 的不同(重要) / import和require区别

  1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。 CommonJS 一旦输出一个值,模块内部的变化就影响不到这个值。而对于 ES6,import 命令时只是生成一个只读引用等到脚本真正执行时,再去被加载的那个模块里面去取值。

  2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口

  3. CommonJS 模块的require()同步加载,ES6 模块的import命令是异步加载。

补充:

JavaScript 现在有两种模块。一种是 ES6 模块,简称 ESM;另一种是 CommonJS 模块,简称 CJS。CommonJS 模块是 Node.js 专用的,与 ES6 模块不兼容。语法上面,两者最明显的差异是,CommonJS 模块使用require()module.exports,ES6 模块使用importexport