JavaScript 模块化01
2018-12-04
最早期JavaScript代码的问题
最早期的时候我们通过<script>
标签来载入JavaScript文件,通过全局变量来区分不同的功能代码, 而全局变量之间的依赖关系需要显式地通过<script>
标签的的加载顺序来解决。
但是过多的<script>
标签数会增加HTTP请求的数目, 从而延长网页的加载时间。 所以一般情况下,我们会在发布时将所有代码压缩到同一个文件中。
当Web项目变得非常庞大,所依赖的前端模块非常多的时候, 通过手动管理这是全局变量就变得不切实际了。
什么是模块化?
Modular programming is a software design technique that emphasizes separating the functionality of a program into independent, interchangeable modules, such that each contains every thing necessary to execute only one aspect of the desired functionality.
为什么要模块化?
- 模块化后的代码可以重复使用, 无需copy&paste相同的代码; 只需要引入相同的模块。
- 高度解耦的模块,更改模块的实现方式时,只需要保证接口一致。
- 避免污染全局空间。
一个模块的基本要求
- 定义封装的模块
- 定义其对其他模块的依赖
- 支持其他模块引入
最早期的JavaScript模块的写法
1 | var module = (function() { |
这种方法是jQuery, zepto.js等早期JavaScript库采用的方法。
这种方式通过闭包的方式封装了私有变量/方法,返回的对象暴露了相应的公共对象和方法。 但它不支持异步加载。所有模块加载都是同步进行的。
CommonJS
CommonJS 是一个同步加载模块规范,通过require
和exports
来引入和输出模块内容。require
function引入其他模块的内容, exports
则是一个代表文件输出内容的对象。
- CommonJS定义的模块分为:
- 模块引用(
require
) - 模块导出对象(
exports
) - 模块标识(
module
)
- 模块引用(
1 | var customerStore = require('store/customer'); // import a module |
Node Module
Node.js的模块系统基本上是遵守CommonJS的规范来实现的。在Node的模块系统中, 每一个文件都是一个独立的模块, 有自己的作用于,在一个文件里定义的变量、函数、类、都是私有的、对其他文件都是不可见的。如下:
1 | // foo.js |
foo.js 文件引入了 circle.js模块, circle.js 模块只导出了area
和circumference
方法。而变量PI
则是circle.js 模块的私有变量, 对foo.js模块不可见。 这是因为在模块被 module wrapper方法处理。
1 | (function(exports, require, module, __filename, __dirname) { |
Node内部提供了一个Module
构建函数,所有模块都是Module
的实例
1 | function Module(id, parent) { |
每个模块内部都有一个module
对象,代表当前模块,它有以下属性:
module.id
: 模块的识别符,通常是带有绝对路径的文件名
module.filename
: 模块的文件名,带有绝对路径
module.loaded
: 返回一个布尔值,表示模块是否完成加载
module.parent
: 返回一个对象,表示调用该模块的的模块
module.children
: 返回一个数组, 表示该模块要用到的其他模块
module.exports
: 表示模块对外的输出值
AMD规范
前面提到CommonJS是一个同步模块加载规范, 只有当引用的模块按顺序加载完成后才会执行后面的操作。 由于CommonJS这一特性, CommonJS只适用于服务器端(模块文件存在于本地硬盘,所以加载起来比较快), 如果是在浏览器环境,需要从服务器端下载响应模块,这种情况下同步加载会阻塞渲染, 所以必须采用非同步加载模块的方式,也就是AMD规范。
AMD使用define方法定义模块:
1 | define(['module1', 'module2'], function(module1, module2) { |
define
方法接受两个参数, 第一个参数是所依赖的模块组成的数组,这些模块以异步非阻塞地方式加载, 当加载完成时,·define`方法的第二个参数——callback function被调用, 加载好的模块作为参数传入callback function.
我们可以注意到,上面的所有模块机制都不是JavaScript的内置的。我们通过CommonJS和AMD来模拟一个模块系统。最终,幸运地是ES6引入了内置的模块系统。
ECMAScript 6 Modules
ECMAScript 6 内置了模块机制,即支持同步也支持异步的模块加载。
1 | // lib.js |
通过import
和export
来引入和导出模块,不能动态加载,在模块中的位置固定在文件的首部和尾部。通过静态分析的方法来构建模块依赖的树。
这种内置模块在一些老的浏览器中还不能使用,需要通过Babel进行转译。