JavaScript 一开始并没有内建模块化支持,也几乎没有模块化这种概念。当时没那么大的需求,搞个模块化显得大材小用啊。随着互联网的发展,尤其是 2006 年 ajax 技术的出现和之后 Web 2.0 的兴起,越来越多的业务逻辑向前端转移,前端开发的复杂程度和代码量逐渐提升。这时,由于缺乏模块化概念,JavaScript 的一些问题便凸显出来:代码难以复用、容易出现全局变量污染和命名冲突、依赖管理难以维护等等。一开始,开发者们使用诸如暴露全局对象、自执行函数等方法来规避这些问题,但仍无法从根本上解决问题。
CommonJS
2009 年,基于将 JavaScript 应用于服务端的尝试,ServerJS 诞生了。之后 ServerJS 更名为 CommonJS,并逐步发展为一个完整的模块规范。简称 CMD(Common Module Definition)
CommonJS 为模块的使用定义了一套 API。比如,它定义了全局函数 require,通过传入模块标识来引入其他模块,如果被引入的模块又依赖了其他模块,那么会依次加载这些模块;通过 module.exports 向外部暴露 API,以便其他的模块引入。
由于 CommonJS 是使用同步方式
加载模块的,即只有加载完成才能进行接下来的操作,因此当应用于浏览器端时会受到网速的限制。
CommonJS对模块的定义主要分为模块引用、模块定义和模块标识3个部分。
1 | const $ = require('jquery') |
AMD
之后,在 CommonJS 组织的讨论中,AMD(Asynchronous Module Definition)应运而生。和 CommonJS 不同的是,它使用异步方式
加载模块,因此更适合被浏览器端采用。AMD 用全局函数 define 来定义模块,它需要三个参数:模块名称、模块的依赖数组、所有依赖都可用之后执行的回调函数(该函数按照依赖声明的顺序,接收依赖作为参数)。
1 | define(['jquery'], function ($) { |
UMD
如果需要同时支持 CommonJS 和 AMD 两种格式,那么可以使用 UMD(Universal Module Definition)。事实上,UMD 通过一系列 if/else 判断来确定当前环境支持的模块体系,因此多数情况下 UMD 格式的模块会占用更大的体积。
1 | (function (root, factory) { |
ES6 Modules
无论是 CommonJS,AMD 还是 UMD,它们都不是标准的 JavaScript 模块解决方案。换句话说,它们都没有被写进 ECMA 的规范中。直到 2015 年 6 月,TC39 委员会终于将 Modules 写进 ECMAScript 2015 中,标志着原生模块新时代的到来。至此,JavaScript 文件有了两种形式:脚本(自 JavaScript 诞生起我们就在使用的)和模块(即 ECMAScript 2015 Modules)。下面就让我们来一起探索 ECMAScript 2015 Modules(以下简称 ES6 Modules)
1 | import $ from('jquery') |
ES6 Modules 现状
时至今日,几大主流浏览器都在积极推进支持原生 ES6 Modules 的工作,部分浏览器的技术预览版也已经初步完成了这一使命。可以通过 caniuse 查看目前浏览器的支持情况。
使用 Babel 和 webpack
由于绝大多数浏览器都不支持 ES6 Modules,所以目前如果想使用它的语法,需要借助 Babel 和 webpack,即通过 Babel 将代码编译为 ES5 的语法,然后使用 webpack 打包成目标格式。
直接使用 ES6 Modules
有些游览器已经支持 ES6 Modules,我们利用 <script type="module">
(默认是 defer)来使用。
其他探索
- 动态加载方案
import()
1 | const load = async (url) => { |
1 | function load (url) { |
- 基于 ES6 Modules 的
module-pusher
尝试