在NodeJS之前,由于没有过于复杂的开发场景,前端是不存在模块化的,后端才有模块化。NodeJS诞生之后,它使用CommonJS的模块化规范。从此,js模块化开始快速发展。
模块化的开发方式可以提高代码复用率,方便进行代码的管理。通常来说,一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数。目前流行的js模块化规范有CommonJS、AMD、CMD、UMD以及ESM的模块系统。
模块化简史
1. 最简单粗暴的方式
function fn1(){
// ...
}
function fn2(){
// ...
}
通过 script 标签引入文件,调用相关的函数。这样需要手动去管理依赖顺序,容易造成命名冲突,污染全局,随着项目的复杂度增加维护成本也越来越高。
2. 用对象来模拟命名空间
var output = {
_count: 0,
fn1: function(){
// ...
}
}
这样可以解决上面的全局污染的问题,有那么点命名空间的意思,但是随着项目复杂度增加需要越来越多的这样的对象需要维护,不说别的,取名字都是个问题。最关键的还是内部的属性还是可以被直接访问和修改。
3. 闭包
var module = (function(){
var _count = 0;
var fn1 = function (){
// ...
}
var fn2 = function fn2(){
// ...
}
return {
fn1: fn1,
fn2: fn2
}
})()
module.fn1();
module._count; // undefined
这样就拥有独立的词法作用域,内存中只会存在一份 copy。这不仅避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域,通过 return 暴露出公共接口供外界调用。这其实就是现代模块化实现的基础。
目前实现模块化的规范主要有:
- CommonJS
- AMD
- CMD
- UMD
- ESM
CommonJs
Node.js 为主要实践者,require 命令用于输入其他模块提供的功能,module.exports命令用于规范模块的对外接口,输出的是一个值的浅拷贝
CommonJS 采用同步加载模块,而加载的文件资源大多数在本地服务器,所以执行速度或时间没问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
// 模块 a.js
const name = "guoteng";
module.exports = {
name,
github: "https://github.com/guoteng",
};
// 模块 b.js
// 引用核心模块或者第三方包模块,不需要写完整路径
const path = require("path");
// 引用自定义模块可以省略.js
const { name, github } = require("./a");
console.log(name, github, path.basename(github));
// 输出 guoteng https://github.com/guoteng guoteng
AMD
RequireJS为主要实践者,define是全局函数,用来定义模块,define(id?, dependencies?, factory)。require 命令用于输入其他模块提供的功能,return 命令用于规范模块的对外接口,define.amd 属性是一个对象,此属性的存在来表明函数遵循 AMD 规范。
AMD采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
// model1.js
define(function() {
console.log("model1 entry");
return {
getHello: function() {
return "model1";
},
};
});
// model2.js
define(function() {
console.log("model2 entry");
return {
getHello: function() {
return "model2";
},
};
});
// main.js
define(function(require) {
var model1 = require("./model1");
console.log(model1.getHello());
var model2 = require("./model2");
console.log(model2.getHello());
});
<script src="https://cdn.bootcss.com/require.js/2.3.6/require.min.js"></script>
<script>
requirejs(["main"]);
</script>
// 输出结果
// model1 entry
// model2 entry
// model1
// model2
CMD
Sea.js为主要实践者,一个文件就是一个模块,可以像 Node.js 一般书写模块代码。主要在浏览器中运行,当然也可以在 Node.js 中运行。
与AMD不同:AMD 推崇依赖前置、提前执行,CMD 推崇依赖就近、延迟执行。
// model1.js
define(function(require, exports, module) {
console.log("model1 entry");
exports.getHello = function() {
return "model1";
};
});
// model2.js
define(function(require, exports, module) {
console.log("model2 entry");
exports.getHello = function() {
return "model2";
};
});
// main.js
define(function(require, exports, module) {
var model1 = require("./model1"); //在需要时申明
console.log(model1.getHello());
var model2 = require("./model2"); //在需要时申明
console.log(model2.getHello());
});
<script src="https://cdn.bootcss.com/seajs/3.0.3/sea.js"></script>
<script>
seajs.use("./main.js");
</script>
// 输出
// model1 entry
// model1
// model2 entry
// model2
UMD
该模式主要用来解决 CommonJS 模式和 AMD 模式代码不能通用的问题,并同时还支持老式的全局变量规范。
- 判断
define为函数,并且是否存在define.amd,来判断是否为 AMD 规范, - 判断
module是否为一个对象,并且是否存在module.exports来判断是否为CommonJS规范 - 如果以上两种都没有,设定为原始的代码规范。
// bundle.js
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined"
? (module.exports = factory())
: typeof define === "function" && define.amd
? define(factory)
: ((global = global || self), (global.myBundle = factory()));
})(this, function() {
"use strict";
var main = () => {
return "hello world";
};
return main;
});
// index.html
<script src="bundle.js"></script>
<script>
console.log(myBundle());
</script>
ESM
JavaScript官方的标准化模块系统,模块的导入导出,通过import和export来确定。
ESM和CommonJs的不同:
- ES modules 输出的是值的引用,输出接口动态绑定,而 CommonJS 输出的是值的拷贝
- ES modules 模块编译时执行,而 CommonJS 模块总是在运行时加载
// index.js
import { name, github } from "./demo.js";
console.log(name(), github());
document.body.innerHTML = `<h1>${name()} ${github()}</h1>`;
export function name() {
return "guoteng";
}
export function github() {
return "https://github.com/guoteng";
}
<script src="./index.js" type="module"></script>
总结
- CommonJs是同步加载,主要用在nodejs环境。
- AMD是异步加载,主要用在浏览器环境。
- CMD和AMD类似,用的不多了。
- UMD是CommonJs、AMD和全局变量的兼容写法。
- ESM是标准,未来支持度会越来越好。