一、模块化开发的理解
模块是实现特定功能的一组属性和方法的封装。
二、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区别
-
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。 CommonJS 一旦输出一个值,模块内部的变化就影响不到这个值。而对于 ES6,import 命令时只是生成一个只读引用,等到脚本真正执行时,再去被加载的那个模块里面去取值。
-
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
-
CommonJS 模块的
require()是同步加载,ES6 模块的import命令是异步加载。
补充:
JavaScript 现在有两种模块。一种是 ES6 模块,简称 ESM;另一种是 CommonJS 模块,简称 CJS。CommonJS 模块是 Node.js 专用的,与 ES6 模块不兼容。语法上面,两者最明显的差异是,CommonJS 模块使用require()和module.exports,ES6 模块使用import和export。