前端模块化

模块化是指将一个复杂的系统分解为多个模块以方便编码。
当项目变得越来越大的时候,原来的引入方式会越来越臃肿,难以维护。
所以在这里需要用模块化的思想来组织代码。目前流行的 前端模块化规范有 CommonJs、AMD、CMD、ES6

CommonJS

CommonJs 是一种被广泛使用的 JavaScript 模块化规范,其核心思想是通过 require 方法来同步加载依赖的其他模块,通过 module.exports 导出需要暴露的接口。它的流行得益于 Node.js 采用了这种方式,后来这种方式就被引入到了 网页开发之中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let num1 = 0;
function add(a, b) {
return a + b;
}

module.exports = {
add,
num1
};
// 导入自定义的模块时,参数包含路径,可省略.js
const moduleA = require("./modul_demo");
moduleA.add(2, 1);

// 在 node 中,引用其核心模块,则不需要带路径
let fs = require("fs");
let data = fs.readFileSync("./modul_demo.js");
data.toString();

特点

commonJS 用同步的方式加载模块,在服务端,模块文件都存在本地磁盘,读取很快,但在浏览器端因为网络等原因,最好的方式应该需要进行异步加载;因为是同步加载,所以只有加载完成,才能执行后面的操作;在服务器端,模块的加载是可以直接运行的;在浏览器端,模块需要提前编译打包处理

机制

CommonJS 模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// lib.js
var counter = 3;
function incCounter() {
counter++;
}

module.exports = {
counter: counter,
incCounter: incCounter
};

// main.js
var mod = require("./lib");

console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3

上面的代码说明,lib.js 模块加载完了之后,它的内部变化就影响不到输出的 mod.counter 了。这是因为 mod.counter 是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// lib.js
var counter = 3;
function incCounter() {
counter++;
}

module.exports = {
get counter() {
return counter;
},
incCounter: incCounter
};

console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 4

CMD

CMD 规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。它整合了 CommonJS 和 AMD 规范的特点最具代表性的是 Sea.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//定义没有依赖的模块
define(function (require, exports, module) {
exports.xxx = value;
module.exports = value;
});

//定义有依赖的模块
define(function (require, exports, module) {
//引入依赖模块(同步)
var module2 = require("./module2");
//引入依赖模块(异步)
require.async("./module3", function (m3) {});
//暴露模块
exports.xxx = value;
});

// 引入使用模块:
define(function (require) {
var m1 = require("./module1");
var m4 = require("./module4");
m1.show();
m4.show();
});

ES6 模块化

ES6 模块化是国际标准化组织 ECMA 提出的 JavaScript 模块化 规范,它在语言层面上实现了模块化。浏览器厂商和 Node.js 都说要原生支持该规范。他将取代 CommonJs 和 Amd 规范,成为浏览器和服务器通用的模块解决方案。

1
2
3
4
5
6
7
8
9
// 导入
import { readFile } from "fs";
import React from "react";

// 导出
export function hello() {}
export default {
// ...
};

缺点

代码无法直接运行在 大部分的 Javascript 运行环境之下,必须通过一些工具转化为浏览器能运行的 ES5

ES6 与 CommonJs 的差异

CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJs 在上文中已经进行分析过,ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
因为 CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

1
2
3
4
5
6
7
8
9
10
11
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}

// main.js
import { counter, incCounter } from "./lib";
console.log(counter); // 3
incCounter();
console.log(counter); // 4