残缺高频题库仓库地址:https://github.com/hzfe/awesome-interview
残缺高频题库浏览地址:https://hzfe.github.io/awesome-interview/
相干问题
- JavaScript 次要有哪几种模块化标准
- AMD / CMD 有什么异同
- ESM 是什么
- 模块化解决了什么问题/痛点
答复关键点
CommonJS AMD CMD UMD ESM
- CommonJS[1]: 次要是 Node.js 应用,通过
require同步加载模块,exports导出内容。 - AMD[2]: 次要是浏览器端应用,通过
define定义模块和依赖,require异步加载模块,推崇依赖前置。 - CMD[3]: 和 AMD 比拟相似,次要是浏览器端应用,通过
require异步加载模块,exports导出内容,推崇依赖就近。 - UMD[4]: 通用模块标准,是 CommonJS、AMD 两个标准的大交融,是跨平台的解决方案。
- ESM[5]: 官网模块化标准,古代浏览器原生反对,通过
import异步加载模块,export导出内容。
知识点深刻
1. 为什么须要模块化和模块化标准
模块化能够解决代码之间的变量、函数、对象等命名的抵触/净化问题,良好的模块化设计能够升高代码之间的耦合关系,进步代码的可维护性、可扩展性以及复用性。
模块化标准的作用是为了标准 JavaScript 模块的定义和加载机制,以对立的形式导出和加载模块,升高学习应用老本,进步开发效率。
2. 各种模块化标准的细节
2.1 CommonJS
CommonJS 次要是 Node.js 应用,通过 require 同步加载模块,exports 导出内容。在 CommonJS 标准下,每一个 JS 文件都是独立的模块,每个模块都有独立的作用域,模块里的本地变量都是公有的。
示例
// hzfe.jsconst hzfeMember = 17;const getHZFEMember = () => { return `HZFE Member: ${hzfeMember}`;};module.exports.getHZFEMember = getHZFEMember;// index.jsconst hzfe = require("./hzfe.js");console.log(hzfe.getHZFEMember()); // HZFE Member: 17应用场景
CommonJS 次要在服务端(如:Node.js)应用,也可通过打包工具打包之后在浏览器端应用。
加载形式
CommonJS 通过同步的形式加载模块,首次加载会缓存后果,后续加载则是间接读取缓存后果。
优缺点
长处
- 简略易用
- 能够在任意地位
require模块 - 反对循环依赖
毛病
- 同步的加载形式不适用于浏览器端
- 浏览器端应用须要打包
- 难以反对模块动态剖析
2.2 AMD (Asynchronous Module Definition)
AMD,即异步模块定义。AMD 定义了一套 JavaScript 模块依赖异步加载规范,用来解决浏览器端模块加载的问题。AMD 次要是浏览器端应用,通过 define 定义模块和依赖,require 异步加载模块,推崇依赖前置。
AMD 模块通过 define 函数定义在闭包中:
/** * define * @param id 模块名 * @param dependencies 依赖列表 * @param factory 模块的具体内容/具体实现 */define(id?: string, dependencies?: string[], factory: Function | Object);示例
// hzfe.jsdefine("hzfe", [], function () { const hzfeMember = 17; const getHZFEMember = () => { return `HZFE Member: ${hzfeMember}`; }; return { getHZFEMember, };});// index.jsrequire(["hzfe"], function (hzfe) { // 依赖前置 console.log(hzfe.getHZFEMember()); // HZFE Member: 17});应用场景
AMD 次要在浏览器端中应用,通过合乎 AMD 规范的 JavaScript 库(如:RequireJs)加载模块。
加载形式
AMD 通过异步的形式加载模块,每加载一个模块理论就是加载对应的 JS 文件。
优缺点
长处
- 依赖异步加载,更快的启动速度
- 反对循环依赖
- 反对插件
毛病
- 语法绝对简单
- 依赖加载器
- 难以反对模块动态剖析
具体实现
- RequireJs
2.3 CMD (Common Module Definition)
CMD,即通用模块定义。CMD 定义了一套 JavaScript 模块依赖异步加载规范,用来解决浏览器端模块加载的问题。CMD 次要是浏览器端应用,通过 define 定义模块和依赖,require 异步加载模块,推崇依赖就近。
CMD 模块通过 define 函数定义在闭包中:
/** * define * @param id 模块名 * @param dependencies 依赖列表 * @param factory 模块的具体内容/具体实现 */define(id?: string, dependencies?: string[], factory: Function | Object);示例
// hzfe.jsdefine("hzfe", [], function () { const hzfeMember = 17; const getHZFEMember = () => { return `HZFE Member: ${hzfeMember}`; }; exports.getHZFEMember = getHZFEMember;});// index.jsdefine(function (require, exports) { const hzfe = require("hzfe"); // 依赖就近 console.log(hzfe.getHZFEMember()); // HZFE Member: 17});应用场景
CMD 次要在浏览器端中应用,通过合乎 CMD 规范的 JavaScript 库(如 sea.js)加载模块。
加载形式
CMD 通过异步的形式加载模块,每加载一个模块理论就是加载对应的 JS 文件。
优缺点
长处
- 依赖异步加载,更快的启动速度
- 反对循环依赖
- 依赖就近
- 与 CommonJS 放弃很大的兼容性
毛病
- 语法绝对简单
- 依赖加载器
- 难以反对模块动态剖析
具体实现
- Sea.js
2.4 UMD (Universal Module Definition)
UMD,即通用模块定义。UMD 次要为了解决 CommonJS 和 AMD 标准下的代码不通用的问题,同时还反对将模块挂载到全局,是跨平台的解决方案。
示例
// hzfe.js(function (root, factory) { if (typeof define === "function" && define.amd) { // AMD define(["exports", "hzfe"], factory); } else if ( typeof exports === "object" && typeof exports.nodeName !== "string" ) { // CommonJS factory(exports, require("hzfe")); } else { // Browser globals factory((root.commonJsStrict = {}), root.hzfe); }})(typeof self !== "undefined" ? self : this, function (exports, b) { const hzfeMember = 17; const getHZFEMember = () => { return `HZFE Member: ${hzfeMember}`; }; exports.getHZFEMember = getHZFEMember;});// index.jsconst hzfe = require("./hzfe.js");console.log(hzfe.getHZFEMember()); // HZFE Member: 17应用场景
UMD 可同时在服务器端和浏览器端应用。
加载形式
UMD 加载模块的形式取决于所处的环境,Node.js 同步加载,浏览器端异步加载。
优缺点
长处
- 跨平台兼容
毛病
- 代码量稍大
2.5 ESM (ECMAScript Module)
ESM,即 ESModule、ECMAScript Module。官网模块化标准,古代浏览器原生反对,通过 import 加载模块,export 导出内容。
示例
// hzfe.jsconst hzfeMember = 17;export const getHZFEMember = () => { return `HZFE Member: ${hzfeMember}`;};// index.jsimport * as hzfe from "./hzfe.js";console.log(hzfe.getHZFEMember()); // HZFE Member: 17应用场景
ESM 在反对的浏览器环境下能够间接应用,在不反对的端须要编译/打包后应用。
加载形式
ESM 加载模块的形式同样取决于所处的环境,Node.js 同步加载,浏览器端异步加载。
优缺点
长处
- 反对同步/异步加载
- 语法简略
- 反对模块动态剖析
- 反对循环援用
毛病
- 兼容性不佳
扩大浏览
1. 动态剖析
动态程序剖析(Static program analysis)是指在不运行程序的条件下,进行程序剖析的办法。 动态程序剖析 - Wiki
简而言之,前文里提到的动态剖析就是指在运行代码之前就可判断出代码内有哪些代码应用到了,哪些没有应用到。
2. 模块化与工程化:webpack
webpack 同时反对 CommonJS、AMD 和 ESM 三种模块化标准的打包。依据不同标准 webpack 会将模块解决成不同的产物。
CommonJS
(function (module, exports) { const hzfeMember = 17; const getHZFEMember = () => { return `HZFE Member: ${hzfeMember}`; }; module.exports = getHZFEMember;});AMD
(function (module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_ARRAY__, // 依赖列表 __WEBPACK_AMD_DEFINE_RESULT__; // factory 返回值 __WEBPACK_AMD_DEFINE_ARRAY__ = []; // 执行 factory __WEBPACK_AMD_DEFINE_RESULT__ = function () { const hzfeMember = 17; const getHZFEMember = () => { return `HZFE Member: ${hzfeMember}`; }; return { getHZFEMember, }; }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__); __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__);});ESM
(function (module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); __webpack_require__.d(__webpack_exports__, "getHZFEMember", function () { return getHZFEMember; }); const hzfeMember = 17; const getHZFEMember = () => { return `HZFE Member: ${hzfeMember}`; };});3. 模块化与工程化:Tree Shaking
Tree shaking 是一个通常用于形容移除 JavaScript 上下文中的未援用代码(dead-code)行为的术语。它依赖于 ES2015 中的 import 和 export 语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件应用。 Tree Shaking - MDN
简略来说,Tree Shaking 是一种依赖 ESM 模块动态剖析实现的性能,它能够在编译时平安的移除代码中未应用的局部(webpack 5 对 CommonJS 也进行了反对,在此不具体开展)。
参考资料
- Modules: CommonJS modules
- Asynchronous module definition
- Common Module Definition
- Universal Module Definition
- Modules: ECMAScript modules
- Module Semantics