模块化演变的过程

111 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情

ES5中的模块加载

 <script type="application/javascript" src="path/to/myModule.js"></script>
 <script src="path/to/myModule.js" defer></script>
 <script src="path/to/myModule.js" async></script>

以上三中js的加载方式,大家都挺熟悉的吧。

  1. 日常最多的模块加载方式。页面是从同步加载的。也就是说,从页面的顶部开始加载。如果遇到js文件过大,有可能会造成页面假死现象。
  2. defer关键字。defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行。
  3. async关键字。async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。如果有多个async,是不会按照页面的加载顺序执行的。

ES6中的模块加载

type属性设为module,浏览器就知道这是一个 ES6 模块。简称 ESM

 <script type="module" src="./foo.js"></script>

浏览器对于带有type="module"<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。

  1. 如果网页有多个<script type="module">,它们会按照在页面出现的顺序依次执行。
  2. 也可以添加async属性。这时只要加载完成,渲染引擎就会中断渲染立即执行。执行完成后,再恢复渲染。如果同时加载多个async文件,顺序也不可以保证。

CommonJS 模块

简称 CJS.CommonJS 模块是 Node.js 专用的,与 ES6 模块不兼容。语法上面,两者最明显的差异是,CommonJS 模块使用require()module.exports,ES6 模块使用importexport

  1. CommonJS规范加载模块是同步的,在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
  2. CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

AMD规范

异步加载模块,主要应用web浏览器。

AMDRequireJS 在推广过程中对模块定义的规范化产物

 /**
  * define
  * @param id 模块名
  * @param dependencies 依赖列表
  * @param factory 模块的具体内容/具体实现
  */
 define(id?: string, dependencies?: string[], factory: Function | Object);
 ​
 define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
     exports.verb = function() {
         // return beta.verb();
         //Or:
         return require("beta").verb();
     }
 });
 ​
 require(["alpha"], function (exports) {
   // 依赖前置
   console.log(exports.verb()); 
 });
  1. AMD推崇依赖前置、提前执行,对于依赖的模块AMD是提前执行。
  2. AMD规范标识:define(id?, dependencies?, factory);

CMD规范

异步加载模块,主要是浏览器端使用 。

CMD是Sea.js推广过程中对模块定义的规范化产物

 /**
  * define
  * @param id 模块名
  * @param dependencies 依赖列表
  * @param factory 模块的具体内容/具体实现
  */
 define(id?: string, dependencies?: string[], factory: Function | Object);
 •
 define('hello', ['jquery'], function(require, exports, module) {
   // 模块代码
 });
 •
 define(function (require, exports) {
   const hello = require("hello"); // 依赖就近
 });
  1. CMD 推崇依赖就近,延迟执行
  2. 模块加载完成后,不会执行其回调函数,而是等到主函数运行且需要的执行依赖的时候才运行依赖函数(依赖后置、按需加载)

UMD规范

UMD 提供了支持多种风格的“通用”模式,在兼容 CommonJS 和 AMD 规范的同时,还兼容全局引用的方式,,是跨平台的解决方案。

可同时在服务器端和浏览器端使用

 (function (window, factory) {
    if (typeof exports === 'object') {
       module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
       define(factory);
    } else {
       window.eventUtil = factory();
    }
 })(this, function () {
    //module ...
 });
  1. UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。
  2. 在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

ES6 模块与 CommonJS 模块的差异

  1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  3. CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段
  4. .mjs文件总是以 ES6 模块加载,.cjs文件总是以 CommonJS 模块加载,.js文件的加载取决于package.json里面type字段的设置。

总结

  1. CommonJS[1]: 主要是 Node.js 使用,通过 require 同步加载模块,exports 导出内容。
  2. AMD[2]: 主要是浏览器端使用,通过 define 定义模块和依赖,require 异步加载模块,推崇依赖前置
  3. CMD[3]: 和 AMD 比较类似,主要是浏览器端使用,通过 require 异步加载模块,exports 导出内容,推崇依赖就近
  4. UMD[4]: 通用模块规范,是 CommonJS、AMD 两个规范的大融合,是跨平台的解决方案。
  5. ESM[5]: 官方模块化规范,现代浏览器原生支持,通过 import 异步加载模块,export 导出内容。

参考文章

前端模块化规范