在面试中只要说到模块化的问题,多多少少总会问到这些,umd、amd、cjs、esm,可能听过其中一个两个,或者都听说过。接下来我们先简单了解一下他们到底是什么,又有什么样的区别呢。
最开始的时候,Javascript是没有导入导出模块的这种方法,这就有一个比较头疼的问题,就是我们所有的代码都要写在一个文件里面,那可真的是又臭又长。有问题了,排查起来还特别的麻烦,定义个变量还总是出各种问题。后来,为了解决这些烦人的问题,各路大佬就推出了umd、amd、cjs、esm、cmd。
模块化的优点
-
实现代码的可复用,具有独立的作用域,避免全局变量被污染
-
便于代码编写和维护,提高开发效率
-
实现按需加载,例如:npm就是一个巨大的模块化仓库
CommonJS(cjs)
CommonJS主要用于服务端,nodejs是默认使用cjs模块规范。每一个文件就是一个模块,有自己独立的作用域、变了、方法等,对其他的模块都不可见。模块可以多次加载,但是只在第一次时运行,然后运行结果就被缓存,后续加载则直接读取缓存结果。若想再次运行模块,则需要清楚缓存。模块的加载顺序是按照在代码中的出现的顺序进行加载的。
如果想要多次执行一个模块,可以导出一个方法,然后直接调用方法即可。特点
-
在执行模块代码之前,NodeJs会使用函数封装器将其封装为闭包形式的方法
-
可以通过 module.exports 导出模块内容,但是不能对exports有赋值操作
-
CJS 是同步导入模块,通过 require(id) 引入模块、JSON、或本地文件
-
被引入的模块将被缓存到require.cache 对象中,如果删除该对象的某个模块会导致下次require的时候重新加载该模块
-
CJS 不能在浏览器中工作,必须经过转换和打包。node.js 就是使用 commonJs 的模块规范,可以在 js 文件中直接使用
// 定义模块 sum.js
const num = 0;
function add(a, b) { return a + b; }
module.exports = {
//在这里写上需要向外暴露的函数、变量
add: add,
num: num
}
/** 必须加./路径,不加的话只会去node_modules文件找 **/
// 引用自定义的模块时,参数包含路径,可省略.js
const sum = require('./sum');
sum.add(2, 5);
// 引用核心模块时,不需要带路径
var http = require('http');
http.createService(...).listen(3000);
amd(Asynchronous Module Definition)
amd是Javascript的异步模块化定义方案,专门为前端而做的,代表库为:require.js。
-
amd是异步导入
-
专为前端而设计
-
语法不如cjs直观
define(['dep1', 'dep2'], function (dep1, dep2) {
//Define the module value by returning a value.
return function () {};
}); // "simplified CommonJS wrapping" https://requirejs.org/docs/whyamd.html
define(function (require) {
var dep1 = require('dep1'),
dep2 = require('dep2');
return function () {};
});
RequireJS 是一个JavaScript模块加载器(文件和模块载入工具),使用RequireJS加载模块化脚本将提高代码的加载速度和质量它针对浏览器使用场景进行了优化,并且也可以应用到其他 JavaScript 环境中,例如 Node.js。
-
引入require.js时,我们会通过
data-main
引入入口文件; -
require.js获取到入口文件后,将文件以及对应的依赖通过
script
标签append到html上; -
依赖是依次、同步append到html,但是script标签的加载却是异步的;
-
依赖加载完成后,会立即调用其回调执行函数;
-
入口文件监听到所有的依赖都加载完成后,再调用其回调函数(即回调函数factory)
My Sample Project
My Sample Project
// main.js requirejs(["helper/util"], function(util) {
// you can do everything you want
});
cmd(Common Module Definition)
cmd通用模块定义,amd的优化版,代表库:sea.js
-
AMD是依赖前置,提前加载依赖;CMD依赖后置,使用时才加载
-
所有代码都运行在模块作用域中,不会污染全局变量;
-
模块会被异步加载;
-
模块加载完成后,不会执行其回调函数,而是等到主函数运行且需要的执行依赖的时候才运行依赖函数(依赖后置、按需加载)
(对于seajs想了解的同学可以自己去官网了解,因为小妖也不太熟悉)
umd(Universal Module Definition)
一般可以把cjs转为UMD,用于浏览器运行时使用。是一种通用的写法,是在amd和cjs两个流行而不统一的规范情况下,才催生出umd来统一规范的,umd前后端均可通用,UMD 更像是一种配置多个模块系统的模式,当使用 Rollup/Webpack 之类的打包器时,UMD 通常用作备用模块。
// CommonJS侧重服务器,而AMD侧重于浏览器,两者的模块不能共享
/* UMD的思想很简单
判断是AMD则使用AMD方式
是commonJS则使用CommonJS方式
都不是则将模块公开给全局(window或global)
*/
(function (root, factory) {
if (typeof define === "function" && define.amd) {
// amd方式
define(["jquery", "underscore"], factory);
} else if (typeof exports === "object") {
// cjs方式
module.exports = factory(require("jquery"), require("underscore"));
} else {
// 公开暴露给全局
root.Requester = factory(root.$, root._);
}
}(this, function ($, _) {
// 属性
var property = Math.property;
// 方法
function a() { }; // 私有方法,因为它没被返回
function b() { return a() }; // 公共方法,因为被返回了
function c(x, y) { return x + y }; // 公共方法,因为被返回了
// 暴露公共方法
return {
ip: PI,
b: b,
c: c
}
}));
esm(ES Module)
ESM 代表 ES 模块,es6原生支持的。这是 Javascript 提出的实现一个标准模块系统的方案。
-
大多现代浏览器都支持
-
异步加载和简单语法
-
因为是静态module结构,支持打包工具去除无用代码
// 在html中调用
// 静态导入:导入本地的文件、库或者远程模块
import { createStore } from "https://unpkg.com/[email protected]/es/redux.mjs";
import * as myModule from './util.js';
// 动态导入:ES模块实际上是JavaScript对象:我们可以解构它们的属性以及调用它们的任何公开方法
btn.addEventListener("click", () => {
// loads named export
import("./util.js")
.then(({ funcA }) => {
funcA(); });
});
或
const loadUtil = () => import("./util.js");
// 返回的是一个 promise。所以也可以使用可以使用 `async/await` btn.addEventListener("click", () => {
loadUtil().then(module => {
module.funcA();
module.default();
})})
// 使用 async/await 的写法
btn.addEventListener("click", async () => {
const utilsModule = await loadUtil();
utilsModule.funcA();
utilsModule.default();
})
最后
-
由于 esm 具有简单的语法,异步特性和可摇树性,因此它是最好的模块化方案
-
umd 随处可见,通常在 esm 不起作用的情况下用作备用
-
cjs 是同步的,适合后端
-
amd 是异步的,适合前端(cmd是amd的优化)
最最后:欢迎关注大家我的公众号