前端模块化:CommonJS,AMD,CMD,import/export
模块化的开发方式可以提高代码复用率,方便进行代码的管理。通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数。目前流行的js模块化规范有CommonJS、AMD、CMD(前三者是ES5中提供的)以及ES6的模块系统import/export。
CommonJS
Node.js是commonJS规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global
。
实际用时,使用module.exports(不推荐使用exports)定义对外输出的API,用require来引用模块。
CommonJS对模块的定义主要分为:模块引用、模块定义、模块标识3个部分。
-
模块引用 ——
const fs = require('fs');
-
模块定义
function fn() {} exports.propName = fn; module.exports = fn;
AMD(Asynchronous Module Definition)
AMD,异步模块定义。AMD不是javascript原生支持,它是RequireJS在推广的过程中对模块定义的范围化的产出。所以使用AMD规范进行页面开发需要用到对应的库,也就是RequireJS。
requireJS主要解决两个问题:
- 多个js文件存在依赖关系时,被依赖的文件需要早于依赖它的文件加载到浏览器
- js加载的时候浏览器会阻塞渲染线程,加载文件越多,页面失去响应的时间越长
CMD(Common Module Definition)
CMD, 通用模块定义。 CMD是在SeaJS推广的过程中产生的,是一个同步模块定义(CMD是依赖就近,在什么地方使用到插件就在什么地方require该插件,即用即返)。在CMD规范中,一个模块就是一个文件。
AMD/CMD比较
-
定义module时对依赖的处理
- AMD推崇依赖前置,在定义的时候就要声明其依赖的模块
- CMD推崇就近依赖,只有在用到这个module的时候才去require
-
加载方式
- AMD: async
- CMD: sync
-
执行module的方式
- AMD加载module完成后就会执行该module,所有module都加载执行完成后会进入require的回调函数,执行主逻辑。依赖的执行顺序和书写的顺序不一定一致,谁先下载完谁先执行,但是主逻辑 一定在所有的依赖加载完成后才执行(有点类似Promise.all)。
- CMD加载完某个依赖后并不执行,只是下载而已。在所有的module加载完成后进入主逻辑,遇到require语句的时候才会执行对应的module。module的执行顺序和书写的顺序是完全一致的。
ES6 Module
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。
其模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
ES6 Modules不是对象,import命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。
ES6模块/CommonJS模块比较
1、CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
- CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
- ES6 Modules 的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的 import 有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
2、CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
- 运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
- 编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。