记录一下前端各种模块化规范的区别和特性。
1、ES6模块
ES6模块
是ECMAScript 2015 提出的模块化规范。他有严格的申明要求。
- 必须在文件的首部
- 不允许使用变量或表达式
- 不允许被嵌入到其他语句中
1.1 使用方式
- 直接使用
<script>
标签加载<script src='xxx' type='module'><>
- 通过import静态引入
import xxx from "module-xxx";
1.2 使用注意
- ES6模块强制自动采用严格模式,所以说不管有没有“user strict”声明都是一样的,换言之,编写代码的时候不必再刻意声明了;
- 虽然大部分主流浏览器支持 ES6 模块,但是和引入普通 JS 的方式略有不同,需要在对应 script 标签中将属性 type 值设置为“module”才能被正确地解析为 ES6 模块;
- 在 Node.js 下使用 ES6 模块则需要将文件名后缀改为“.mjs”,用来和 Node.js 默认使用的 CommonJS 规范模块作区分。
1.3 特性
ES6 模块有两个重要特性一定要掌握,一个是值引用
,另一个是静态声明
。
- 值引用:值引用是指
export
语句输出的接口与其对应的值是动态绑定的关系,意思是通过他获取的值会随着模块内部的修改而改变,类似于对象的浅拷贝。 - 静态声明:静态分析是指不需要执行代码,只从字面量上对代码进行分析。好处是方便优化代码体积,比如通过
Tree-shaking
操作消除模块中没有被引用或者执行结果不会被用到的无用代码。
1.4 动态导入
静态导入可以直接import
,动态导入就需要import()
了。调用 import()
函数传入模块路径,得到一个Promise
对象。
let promise = import("module-promise");
同时动态导入还支持await / async
let promise = await import("module-promise");
在webpack
打包的时候,在调用了import()
的地方,请求的模块及其子模块会被分到单独的chunk里面。
2、CommonJs
CommonJS
最初名为Server.js
,是为浏览器之外的 JavaScript 运行环境提供的模块规范,最终被Node.js采用。
CJS
规定每一个文件就是一个模块,有独立的作用域,模块内部的this也只是当前模块。
CJS
导出的对象具有以下属性
id
模块标识符,通常是带有绝对路径的模块文件名filename
模块的文件名,带有绝对路径loaded
返回一个布尔值,表示是否完成加载。parent
返回一个对象,表示调用该模块的模块。children
返回一个数组,表示该模块用到的其他模块。exports
表示模块对外输出的值。
使用
// a.js
const fn = function () {
console.log('Hello');
}
module.exports = fn;
// b.js
const fn = require('./a');
fn(); // Hello
引用模块则需要通过 require 函数,它的基本功能是,读入并执行一个 JavaScript 文件,然后返回该模块的 exports 对象。
2.1 特性
CJS
的特性和ES6
的恰恰相反,它采用的是值拷贝和动态声明。
-
值拷贝:一旦输出了一个值,模块内部的变化就不会影响到这个值了。
-
动态声明:可以随意在表达式语句中引用模块。
3、AMD
AMD是在ES6出来之前流行的浏览器模块化方案。
AMD规定只定义了一个全局函数define
,通过它可以定义和引用模块,它有3个参数:
define(id?, dependencies?, factory)
- 第 1 个参数 id 为模块的名称,该参数是可选的。如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字;如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。
- 第 2 个参数 dependencies 是个数组,它定义了所依赖的模块。依赖模块必须根据模块的工厂函数优先级执行,并且执行的结果应该按照依赖数组中的位置顺序以参数的形式传入(定义中模块的)工厂函数中。
- 第 3 个参数 factory 为模块初始化要执行的函数或对象。如果是函数,那么该函数是单例模式,只会被执行一次;如果是对象,此对象应该为模块的输出值。
3.1 特性
AMD的重要特性就是异步加载
,同步并发加载依赖的模块,当所有的模块都加载完成后,再执行当前模块的回调函数。
由于AMD不是浏览器支持的模块化规范,所以AMD的实现由第三方库来实现,比如RequireJs
。它的核心是两个全局函数 define 和 require,define 函数可以将依赖注入队列中,并将回调函数定义成模块;require 函数主要作用是创建 script 标签请求对应的模块,然后加载和执行模块。
4、CMD
CMD(Common Module Definition,通用模块定义)是基于浏览器环境制定的模块规范。
CMD定义模块是通过一个全局的defind
实现的,但是只有一个参数。如果这个参数是对象,那么模块导出的就是对象;如果这个参数为函数,那么这个函数会被传入 3 个参数 require 、 exports 和 module。
define(factory);
CMD 最大的特点就是懒加载,不需要在定义模块的时候声明依赖,可以在模块执行时动态加载依赖。当然还有一点不同,那就是CMD同时支持同步加载模块和异步加载模块。
用一句话来形容就是,它整合了 CommonJS 和 AMD 规范的特点。遵循 CMD 规范的代表开源项目是 sea.js ,它的实现和 requirejs 没有本质差别。
5、UMD
UMD(Universal Module Definition,统一模块定义)其实并不是模块管理规范,而是带有前后端同构思想的模块封装工具。通过 UMD 可以在合适的环境选择对应的模块规范。比如在 Node.js 环境中采用 CommonJS 模块管理,在浏览器端且支持 AMD 的情况下采用 AMD 模块,否则导出为全局函数。
实现原理:
- 先判断是否支持 Node.js 模块格式(exports 是否存在),存在则使用 Node.js 模块格式;
- 再判断是否支持 AMD(define 是否存在),存在则使用 AMD 方式加载模块;
- 若前两个都不存在,则将模块公开到全局(Window 或 Global)。
6、ES5环境下编写模块
模块的核心就是创建独立作用域,要实现这个目的,就需要通过函数来实现。可以通过定义一个立即执行函数来返回一个对象。