以前没有模块化时,我们可能会按如下方式划分模块:
通过 <script>
标签引入各个文件,把每个文件看成是一个模块,每个模块的接口通常是暴露在全局作用域下的,也就是定义在 window 对象中。
<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="module3.js"></script>
如果通过这种方式做模块化,当项目变得越来越大时,很容易造成全局变量冲突,项目也会变得越来越难以管理。
因此我们需要找到一种合适的方式来帮助我们模块化,模块化能给我们带来哪些好处呢?具体来说,主要包含以下几个方面:
require(dependency)
,通过传入模块标识来引入其他依赖模块,执行的结果即为别的模块暴漏出来的 API。导出模块:
// math.js
var num = 0;
function add(a, b) {
return a + b;
}
module.exports = {
num: num,
add: add
}
加载模块:
// 引入自定义的模块时,参数包含路径,可省略.js
// 引入核心模块时,不需要带路径,如var http = require("http");
var math = require('./math');
math.add(1, 2) //3
代表作 RequireJS。
define(id, dependencies, factory)
,用于定义模块。dependencies 为依赖的模块数组,在 factory 中需传入形参与之一一对应。exports.XModule = XModule、module.exports = XModule
。定义模块:
define({
method1: function() {},
method2: function() {},
});
// 函数的返回值就是输出的模块
define(function () {
return {
method1: function() {},
method2: function() {},
};
});
define(['module1', 'module2'], function(m1, m2) {
...
});
// module1模块和module2模块指的是,当前目录下的module1.js文件和module2.js文件,等同于写成['./module1', './module2']
需要注意的是,回调函数必须返回一个对象,这个对象就是你定义的模块。
调用模块:
require(['foo', 'bar'], function ( foo, bar ) {
foo.doSomething();
});
* RequireJS 从 2.0 开始,也改成了可以延迟执行。
CMD 是 sea.js
在推广过程中对模块定义的规范化产出,属于 CommonJS 的一种规范。
定义模块:
define(function (require, exports, module) {
var add = function (a, b) {
return a + b;
}
exports.add = add;
})
使用模块:
seajs.use(['math.js'], function (math) {
var sum = math.add(1, 2);
});
UMD 是一种 JavaScript 通用模块定义规范,让你的模块能在 JavaScript 所有运行环境中发挥作用。
规定如下:
导出模块:
// 导出
exportfunction hello() { };
exportdefault {
// ...
};
引入模块:
import { readFile } from'fs';
import React from'react';
浏览器的支持情况
2009 年的时候,Mozilla 的工程师 Kevin Dangoor 与同事们一起制定了一套 JsaveScript 模块化标准,并取名为 ServerJS。ServerJS 最早是用于服务端的,目的是为了在自动化测试的工作中提供模块化导入的功能。之后 ServerJS 更名为了 CommonJS。
同年 9 月,Ryan Dahl 创造了 Node.js,而一开始 Node.js 还没有包管理工具,随后采用 CommonJS 规范的 npm (即 node package manager) 诞生了。随着 Node.js 的快速发展,CommonJS 规范也渐渐进入广大前端开发者的视野。至此,JavaScript 第一个模块化规范,也正式登入历史舞台。
随着 npm 的流行,广大前端开发者也希望引入这种模块化的方案到日常的开发工作中。但是 CommonJS 只能应用于服务端,因此势必需要重新制定规范标准。而此时,关于如何制定新的标准,主要有三大流派:
CommonJS 已经在服务端成功应用了,那么在浏览器加载模块前,我们先通过工具将模块转换成浏览器能够执行的代码就可以了。该想法与如今的 babel 等工具思路是相似的,通过将高版本的代码转换为低版本的代码,目的都是为了兼容。而 browserify 就是这一观点下的产物。
浏览器与服务端存在很大的差异,我们应该根据浏览器的特点,放弃 require 的方式而是使用回调的方式引入模块。将同步加载模块更改为异步加载模块。
CommonJS 中的 require 等规范还是有可取之处,在尽可能与现有的 CommonJS 规范保持一致的前提下,我们也可以引入一些好的特性,比如 exports 可以导出更多类型而不是局限于 Object。
激进派的 James Burke 在 2009 年开发出了 RequireJS 模块加载器。2011 年,在 RequireJS 社区的基础上,诞生了 AMD(Async Module Definition)社区。AMD 是第一个支持浏览器端的 Javascript 模块化解决方案,RequireJS 迅速被广大开发者熟知和采用。
中间派的故事比较曲折。CommonJS 的主要贡献者之一 Wes Garland 给出了一个名为 BravoJS 的实现。Wes Garland 本人是学院派,理论功底十分强,但写出的作品却不很实用。另一位实战派大佬提出了 Modules/Wrappings 的方案,并给出了一个名为 FlyScript 的实现。而两位大佬对具体的实现发生了一些争论,最后以 FlyScript 的 GitHub 仓库被删除而结束这段恩怨。
2011 年 4 月,阿里巴巴的前端大佬玉伯,因为给 RequireJS 提出的建议被不断拒绝后,在参考了 AMD 和 CommonJS 的方案后,便自己写了一个模块加载器 Sea.JS,同时提出了 CMD 规范。CMD 规范的主要内容与 AMD 相似,但是保留了 CommonJS 中延迟加载和就近声明的特性。
2014 年 9 月,美籍华裔 Homa Wong 提交了 UMD 第一个版本的代码。UMD 即 Universal Module Definition 的缩写,它本质上并不是一个真正的模块化方案,而是将 CommonJS 和 AMD 相结合。
2016 年 5 月,经过了两年的讨论,ECMAScript 6.0 终于正式通过决议,成为了国际标准。在这一标准中,首次引入了 import 和 export 两个 JavaScript 关键字,并提供了被称为 ES Module 的模块化方案。2017 年 9 月, Chrome 的 61.0 版本首次在浏览器端支持了 ES Module。目前已经有很多浏览器原生支持了 ES Module。而伴随着 ES Module 的兴起,也出现了像 Vite 这样的构建工具。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8