本文系统梳理JavaScript模块化发展历程,从解决全局污染问题的Module模式入手,深入解析CommonJS、AMD、CMD及ES6 Module四大核心机制。重点对比各规范在加载方式、依赖处理及输出特性上的差异,帮助开发者理解Node.js与浏览器端模块化实现的底层逻辑与适用场景。
JavaScript模块化概述
模块化是JavaScript语言发展过程中的必然阶段,其核心价值在于协助开发者对代码进行拆分与结构化组织。
Module模式:封装与私有化
Module模式
在标准化的模块化规范确立之前,JavaScript开发者主要依赖Module设计模式来规避全局作用域污染。该模式最初源于传统软件工程,旨在为类提供私有与公有的封装机制。在JavaScript语境下,Module模式利用匿名函数自调用(即闭包)实现封装,并通过自定义暴露行为来明确区分私有成员与公有成员。
let myModule = (function (window) {
let moduleName = 'module' // private
// public
function setModuleName(name) {
moduleName = name
}
// public
function getModuleName() {
return moduleName
}
return { setModuleName, getModuleName } // 暴露行为
})(window)
上述示例展示了Module模式的一种实现方式,它利用闭包特性创建独立作用域,从而缓解了全局命名冲突与安全性隐患。然而,该模式无法直接用于代码的组织与拆分,这促使了后续模块化规范的诞生。
CommonJS规范详解
模块化规范
1. CommonJS
CommonJS规范主要应用于Node.js开发环境,其核心概念是每个文件视为一个独立模块,拥有专属作用域。模块通过module.exports暴露公有成员。例如:
// 文件名:x.js
let x = 1;
function add() {
x += 1;
return x;
}
module.exports.x = x;
module.exports.add = add;
此外,CommonJS利用require()函数引入模块依赖,该函数支持引入Node内置模块、自定义模块以及npm第三方模块。
// 文件名:main.js
let xm = require('./x.js');
console.log(xm.x); // 1
console.log(xm.add()); // 2
console.log(xm.x); // 1
从上述代码可见,require函数同步加载x.js,并返回module.exports输出字面量的拷贝值。有人可能疑惑:module.exports.x = x;看似赋值,为何如此运作?实际上,Module模式是模块化规范的基石,而CommonJS正是对Module模式的一种封装实现。我们可以完全使用Module模式复现上述代码效果:
let xModule = (function (){
let x = 1;
function add() {
x += 1;
return x;
}
return { x, add };
})();
let xm = xModule;
console.log(xm.x); // 1
console.log(xm.add()); // 2
console.log(xm.x); // 1
通过Module模式模拟CommonJS原理,可以清晰解释其特性。由于CommonJS需通过赋值获取匿名函数自调用的返回值,因此require函数在加载模块时表现为同步。然而,这种加载机制限制了CommonJS在客户端的应用,因为通过HTTP同步加载模块耗时极高。
AMD与CMD规范对比
2. AMD和CMD
2.1 AMD
// 定义AMD规范的模块
define([function() {
return 模块
})
与CommonJS不同,AMD规范中依赖的模块采用异步加载方式,定义的模块则作为回调函数执行,且依赖于require.js模块管理工具库。尽管AMD不采用匿名函数自调用进行封装,但仍可利用闭包原理实现模块私有与公有成员的区分:
define(['module1', 'module2'], function(m1, m2) {
let x = 1;
function add() {
x += 1;
return x;
}
return { add };
})
2.2 CMD
CMD是SeaJS在推广过程中对模块定义的规范化产出。AMD推崇依赖前置,而CMD推崇依赖就近。
define(function(require, exports, module) {
// 同步加载模块
var a = require('./a');
a.doSomething();
// 异步加载一个模块,在加载完成时,执行回调
require.async(['./b'], function(b) {
b.doSomething();
});
// 对外暴露成员
exports.doSomething = function() {};
});
// 使用模块
seajs.use('path');
CMD集成了CommonJS与AMD的特点,同时支持同步和异步模块加载。CMD在加载依赖模块后并不立即执行,仅完成下载;待所有依赖模块加载完毕后进入主逻辑,遇到require语句时才执行对应模块。这使得模块执行顺序与书写顺序完全一致,且在CMD中同步加载模块时不存在HTTP请求过程。
ES6 Module特性分析
ES6 module
ES6的模块化已不再是外部规范,而是JavaScript语言内置特性。随着ES6的推出,AMD和CMD逐渐退出历史舞台。相比传统模块化规范,ES6模块具备两大显著特点:
- 模块化规范输出的是值的拷贝,而ES6模块输出的是值的引用。
- 模块化规范采用运行时加载,ES6模块则在编译时输出接口。
模块化规范输出的是一个对象,该对象仅在脚本运行结束后生成;而ES6模块并非对象,ES6 module采用多对象输出与多对象加载模型。从原理看,模块化规范是匿名函数自调用的封装,而ES6 module则是利用匿名函数自调用去调用输出的成员。两者区别可参考Babel and CommonJS modules。
以上为本次介绍的全部知识点,感谢大家的学习和对本站的支持。