前端模块化规范

完整高频题库仓库地址: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.js
const hzfeMember = 17;
const getHZFEMember = () => {
  return `HZFE Member: ${hzfeMember}`;
};
module.exports.getHZFEMember = getHZFEMember;

// index.js
const 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.js
define("hzfe", [], function () {
  const hzfeMember = 17;
  const getHZFEMember = () => {
    return `HZFE Member: ${hzfeMember}`;
  };

  return {
    getHZFEMember,
  };
});

// index.js
require(["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.js
define("hzfe", [], function () {
  const hzfeMember = 17;
  const getHZFEMember = () => {
    return `HZFE Member: ${hzfeMember}`;
  };

  exports.getHZFEMember = getHZFEMember;
});

// index.js
define(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.js
const 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.js
const hzfeMember = 17;
export const getHZFEMember = () => {
  return `HZFE Member: ${hzfeMember}`;
};

// index.js
import * 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 也进行了支持,在此不详细展开)。

参考资料

  1. Modules: CommonJS modules
  2. Asynchronous module definition
  3. Common Module Definition
  4. Universal Module Definition
  5. Modules: ECMAScript modules
  6. Module Semantics

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 早期的前端技术标准根本没有预料到前端会有今天这样的规模,所以很多设计会使我们在开发过程中遇到很多模块化的问题,虽然...
    汤姆威廉斯阅读 752评论 0 0
  • 前言  随着前端的发展,前端代码日益复杂庞大,此时在JS方面就会考虑使用模块化规范去管理。下面主要介绍开发中流行的...
    agamgn阅读 359评论 0 1
  • 前言 现在学习到webpack,然后重新回过头来回顾前端模块化一路发展过程定义的规范,遂写下这篇文。 一、模块化的...
    huangyh_max阅读 1,123评论 0 4
  • 模块化的出现 在JavaScript发展初期就是为了实现简单的页面交互逻辑,寥寥数语即可;如今CPU、浏览器性能得...
    E1FANG阅读 282评论 0 0
  • 以下皆为拉勾教育课件内笔记 模块化演变 文件划分方式(1999)具体做法就是将每个功能,各自单独放到不同的文件中,...
    白棠阅读 192评论 0 0