跳到主要内容

模块化

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);
  }
 };
});

参数列表

  1. 新创建模块的ID。使用该ID,可以在系统的其他部分引用该模块。
  2. 当前模块依赖的模块ID列表。
  3. 初始化模块的工厂函数,该工厂函数接收依赖的模块列表作为参数。

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属性,我们不得不采用浏览器支持的格式打包代码,可以通过BrowserifyRequireJS来实现。

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";通过别名导入模块中声明的全部导出内容

参考
https://segmentfault.com/a/1190000004873947