细说JS模块化规范(CommonJS、AMD、CMD、UMD、ES6 Module)

3,992 阅读10分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战


模块化

模块化及其优点

模块化就是将系统分离成独立功能的模块,这样我们需要什么功能,就加载什么功能。

模块化的优点

  • 避免命名空间的冲突(减少命名空间的污染)
  • 更好的分离,实现按需加载
  • 提高可代码的复用性
  • 提高了代码的维护性

JS模块化大致发展过程

CommonJS(服务端) =>AMD(浏览器) => CMD => ES6 Module模块化

模块化规范的种类

image.png

模块化规范的发展趋势

image.png 前端模块规范有三种:CommonJs,AMD和CMD。
CommonJs用在服务器端,AMD和CMD用在浏览器环境
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。\

AMD:提前执行(异步加载:依赖先执行)+延迟执行
CMD:延迟执行(运行到需加载,根据顺序执行)

CommonJS 规范

CommonJS 的概念

Node 应用由模块组成,采用 CommonJS 模块规范。

每个文件就是一个独立模块有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。

  • 每个文件都可以作为一个模块(这里的文件指的是js文件)
  • 在服务器端:模块的加载是运行时同步加载的
  • 在浏览去端:模块需要提前编译打包处理,不然浏览器不能识别require语法

CommonJS 的使用

主要分为定义模块和引入模块两个步骤。

定义模块

CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。

// 通过`module.exports`输出变量`x`和函数`addX`
let x = 1;
let addX = function (value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;

引入模块

require方法用于加载模块。

var example = require('./example.js');

CommonJS 的特点

  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载是一项阻塞操作,也就是同步加载。模块加载的顺序,按照其在代码中出现的顺序。

模块的加载机制

CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

AMD 规范

ADM 概念

AMD规范全称是Asynchronous Module Definition,即异步模块加载机制,主要用于浏览器。它完整描述了模块的定义,依赖关系,引用关系以及加载机制。由于该规范不是原生js支持的,使用AMD规范进行开发的时候需要引入第三方的库函数,AMD对应的就是鼎鼎大名的RequireJS

  • CommonJS规范出现后,在Node开发中产生了非常好的效果,开发者希望借鉴这个经验来解决浏览器JS的模块化
  • 但是大部分人认为浏览器和服务器的环境差别太大,毕竟浏览器JS是通过网络动态以此加载的,而服务器的JS是保存在本地磁盘中。因此浏览器需要实现异步加载,模块在定义的时候就必须先知名它所需要依赖的模块,然后把本模块的代码写在回调函数中执行,最终衍生出了AMD规范
  • AMD的主要思想时异步模块,主逻辑在函数回调中执行。

AMD 的使用

define和require这两个定义模块、调用模块的方法,合称为AMD模式。它的模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。

使用define用来暴露模块,使用require用来引入模块;require()异步加载模块,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。

AMD模式可以用于浏览器环境,并且允许异步加载模块,也可以根据需要动态加载模块。

define方法:定义模块

define方法用于定义模块,RequireJS要求每个模块放在一个单独的文件里。 按照是否依赖其他模块,可以分成两种情况讨论。第一种情况是定义独立模块,即所定义的模块不依赖其他模块;第二种情况是定义非独立模块,即所定义的模块依赖于其他模块。

require方法:调用模块

require方法用于调用模块。它的参数与define方法类似。

RequireJS主要解决了两个问题:

  1. 多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
  2. js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长

AMD规范与CommonJS规范的兼容性

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。

CMD 规范

CMD 的概念

CMD(Common Module Definition) ,通用模块定义,它解决的问题和AMD规范是一样的,只不过在模块定义方式和模块加载时机上不同,CMD也需要额外的引入第三方的库文件,SeaJS,SeaJS推崇一个模块一个文件

  • AMD/RequireJS的JS模块实现有很多不优雅的地方,主要原因不能以一种更好的管理模块的依赖加载和执行;
  • 那么就出现了SeaJS,SeaJs遵循的是CMD规范,CMD规范在AMD的基础上改进的一种规范,解决了AMD对依赖模块的执行时机的问题;
  • SeaJS模块化的顺序是:模块化预加载=》主逻辑调用模块时才执行模块中的代码;
  • SeaJS的用法和AMD基本相同,并且融合了CommonJS的写法;

CMD 的使用

(对于模块的引入,具有同步和异步两中方式)

define 是一个全局函数,用来定义模块

SeaJS提供了seajs.use来加载模块

SeaJS的出现,是CommonJS在浏览器的践行者,并吸收了RequireJS的优点

UMD(AMD + CommonJS)

CommonJS 适用于服务端,AMD、CMD 适用于web端,那么需要同时运行在这两端的模块就可以采用 UMD 的方法,使用该模块化方案,可以很好地兼容AMD、CommonJS语法。UMD 先判断是否支持Node.js的模块(exports)是否存在,存在则使用 node.js 模块模式。再判断是否支持 AMD(define是否存在),存在则使用AMD方式加载模块。由于这种通用模块解决方案的适用性强,很多JS框架和类库都会打包成这种形式的代码。

ES6中的Module模块

仅支持静态导入导出

ES6 规范只支持静态的导入和导出,ES Module 需要在编译时期进行模块静态优化,也就是必须要在编译时就能确定。

为什么要这么做,主要是两点:

  1. 性能,在编译阶段即完成所有模块导入,如果在运行时进行会降低速度
  2. 更好的检查错误,比如对变量类型进行检查

ES6 Module 使用

  • 模块功能主要由两个命令构成:exportimport
  • export用于暴露接口,import用于引入模块

在使用 ES Module 值得注意的是:import 和 export 命令只能在模块的顶层,在代码块中将会报错,这是因为 ES Module 需要在编译时期进行模块静态优化,import 和 export 命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行,这种设计有利于编译器提高效率,但也导致无法在运行时加载模块(动态加载)。

对于这个缺点,TC39 有了一个新的提案 – Dynamic Import,提案的内容是建议引入 import()方法,实现模块动态加载。

// specifier: 指定所要加载的模块的位置
import(specifier)
  • import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。 它是运行时执行,也就是说,什么时候运行到这句话,就会加载到指定的模块。另外,import()函数所加载的模块没有静态链接关系,这点也是与import语法不同

  • 注意的时ES6 的Module语法有些浏览器是不支持的,因此需要Babel先进性转码,将import和export命令转成ES5语法才能被浏览器解析。

CommonJS、AMD、CMD、ES6 Module的区别

AMD与CMD区别

  1. 模块定义时对依赖的处理不同

    AMD推崇前置依赖,在定义模块时就要声明其依赖的模块;而CMD推从就近依赖,只有在用到某个模块时再使用require导入;

  2. 对依赖模块的处理机制不同

    首先AMD和CMD对模块的加载方式都是异步的 不过区别在于AMD当加载了依赖模块之后立即执行依赖模块,依赖模块的执行顺序和我们书写的顺序不一定一致; 而CMD加载完依赖模块之后,并不会立即执行,等所有的依赖模块都加载好之后,进入回到函数逻辑,遇到require语句的时候,才执行对应的模块,这样模块的执行顺序就和我们书写的时候一致了

ES6模块与CommonJS模块加载的区别

CommonJS时运行时加载,因为ComminJS加载是先加载整个模块,生成一个对象(这个对象包含了path这个模块的所有API),然后再从这个对象上面读取方法-----运行时加载 ES6是编译时加载ES6模块不是对象,它的对外接口只是一种静态定义,在代码静态定义阶段就会生成-----编译时加载

//ES6模块
import { basename, dirname, parse } from 'path';

//CommonJS模块
let { basename, dirname, parse } = require('path');

以上这种写法与CommonJS的模块加载有什么不同?

  • 当require path 时,CommonJS会将path模块运行一遍,并返回一个对象,这个对象包含path模块的所有API。

总结

模块化就是将系统分离成独立功能的模块,这样我们需要什么功能,就加载什么功能。

模块化的优点

  • 避免命名空间的冲突(减少命名空间的污染)
  • 更好的分离,实现按需加载
  • 提高可代码的复用性
  • 提高了代码的维护性

JS模块化发展产物:CommonJS(服务端) =>AMD(浏览器) => CMD => ES6 Module模块化

CommonJs主要用在服务器端,AMD和CMD用在浏览器环境 AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。 CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。 ES6 规范只支持静态的导入和导出,与CommonJS 的运行时加载不同的是ES6是编译时加载,ES Module 是在编译时期进行模块静态优化。


如果这篇文章帮到了你,记得点赞👍收藏加关注哦😊,希望点赞多多多多...

文中如有错误,欢迎在评论区指正