模块化
AMD/CMD/CommonJS/UMD
在JavaScript ES6之前,没有内置的模块,开发者们不得不创造性地发挥JavaScript语言现有的特性实现模块化。最流行的方式之一是通过立即执行函数的闭包实现模块。
CommonJS和ESM区别
CommonJs模块输出的是值的拷贝,ES6模块输出的是值的引用
CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
使用对象、闭包和立即执行函数实现模块
- 使用立即执行函数创建定义模块变量的闭包,从外部作用域无法访问这些变量。
- 使用闭包可以使模块变量保持活跃。
最流行的是模块模式,通常采用立即执行函数,并返回一个新对象作为模块的公共接口。
const MouseCounterModule = function() { //创建一个全局模块变量,赋值为立即实行函数的执行结果
let numClicks = 0; //创建模块私有变量
const handleClick = () => {
console.log(++numClicks);
}; //创建模块私有函数
return {
countClicks: () => {
document.addEventListener("click", handleClick);
} //返回一个对象,代表模块的接口。通过闭包,可以访问模块私有变量和方法
};
}();
/* 在立即执行函数内部,定义模块内部的实现细节:一个局部变量numClicks,一个局部函数handleClick,都只能在模块内部访问。然后,我们创建并立即返回一个对象作为模块的“公共接口”。该接口包括countClicks方法,通过该方法我们可以从模块外部访问模块内部的功能。
由于暴露了模块接口,模块内部细节仍然可以通过接口创建的闭包保持活跃。
*/
//扩展模块
(function(module) { //立即调用一个函数,该函数接收需要扩展的模块作为参数
let numScrolls = 0;
const handleScroll = () => {
console.log(++numScrolls);
} //定义新的私有变量和函数
module.countScrolls = () => {
document.addEventListener("wheel", handleScroll);
}; //扩展模块接口
})(MouseCounterModule);
- 这种方式无法共用模块间的变量
- 无法处理模块的依赖关系
AMD
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。AMD最流行的实现是RequireJS
//依赖一开始就写好
define('MouseCounterModule',['jQuery'], $ => { // 使用define函数指定模块及其依赖,模块工厂函数会创建对应的模块
let numClicks = 0;
const handleClick = () => {
console.log(++numClicks);
};
return { // 模块的公共接口
countClicks: () => {
$(document).on("click", handleClick);
}
};
});
参数列表
- 新创建模块的ID。使用该ID,可以在系统的其他部分引用该模块。
- 当前模块依赖的模块ID列表。
- 初始化模块的工厂函数,该工厂函数接收依赖的模块列表作为参数。
CMD
CMD推崇依赖就近,AMD推崇依赖前置。
define(function (requie, exports, module) {
var $ = require('jQuery');//依赖就近
let numClicks = 0;
const handleClick = () => {
console.log(++numClicks);
};
return { // 模块的公共接口
countClicks: () => {
$(document).on("click", handleClick);
}
};
});
CommonJS
CommonJS设计面向通用JavaScript环境。模块定义基于文件,所以每个文件中只能定义一个模块。CommonJS提供变量module,该变量具有属性exports,通过exports可以很容易地扩展额外属性。最后,module.exports作为模块的公共接口。
- 语法简单。只需定义module.exports属性,剩下的模块代码与标准JavaScript无差异。引用模块的方法也很简单,只需要使用require函数。
- CommonJS是Node.js默认的模块格式,所以我们可以使用npm上成千上万的包。
//MouseCounterModule.js
const $ = require("jQuery"); // 同步引入jQuery模块
let numClicks = 0;
const handleClick = () => {
alert(++numClicks);
};
module.exports = { // 使用module.exports定义模块公共接口
countClicks: () => {
$(document).on("click", handleClick);
}
};
//使用模块
const MouseCounterModule = require("MouseCounterModule.js");
MouseCounterModule.countClicks();
CommonJS最大的缺点是不显式地支持浏览器。浏览器端的JavaScript不支持module变量及export属性,我们不得不采用浏览器支持的格式打包代码,可以通过Browserify或RequireJS来实现。
UMD
AMD模块以浏览器第一的原则发展,异步加载模块。
CommonJS模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。
这迫使人们又想出另一个更通用的模式UMD (Universal Module Definition)。希望解决跨平台的解决方案。
UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。 在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。
import Report from './report.js';
;(function (name, definition) {
if (typeof define === 'function') {// AMD环境或CMD环境
define(definition);
} else if (typeof module !== 'undefined' && module.exports) {// 检查上下文环境是否为Node
module.exports = definition();
} else {// 将模块的执行结果挂在window变量中,在浏览器中this指向window对象
Function('return this')()[name] = definition();
}
})('frontError', function () {
return Report
});
ES6模块化语法
代 码 | 含 义 |
---|---|
export const ninja = "Yoshi"; | 导出变量 |
export function compare(){} | 导出函数 |
export class Ninja{} | 导出类 |
export default class Ninja{} | 导出默认类 |
export default function Ninja(){} | 导出默认函数 |
const ninja = "Yoshi"; function compare(){}; export {ninja, compare}; | 导出存在的变量 |
export {ninja as samurai, compare}; | 使用别名导出变量 |
import Ninja from "Ninja.js"; | 导入默认导出 |
import {ninja, Ninja} from "Ninja.js"; | 导入命名导出 |
import * as Ninja from "Ninja.js"; | 导入模块中声明的全部导出内容 |
import {ninja as iNinja} from "Ninja.js"; | 通过别名导入模块中声明的全部导出内容 |