一、前言
模块化其实很早就在很多高级语言中如Java
、Ruby
、Python
出现了,甚至在C
语言都有类似的模块化,比如include
语句引入头文件,各种库等等。而在前端中,JavaScript作为主要语言,它设计之初并没有实现模块化。随着Web的发展,JavaScript地位越来越高,同时也出现了以下主要问题:
- 代码难以维护
- 作用域污染
- 无法唯一标识变量
- …
这可谓是一大痛点呐!但是广大的软件工程师们也不是吃素的,于是解决方案如AMD
、CMD
、ES Module
、CommonJS
便雨后春笋般涌现出来了。
现如今AMD
、CMD
已经慢慢淡出我们的视野了,我们接触最多就是两种模块规范:ES Module
和CommonJS
,前者应用在ECMAScript中而后者在Node中。
CommonJS规范是一个超级大的概念,和ECMAScript规范一样,它是整个语言层面的规范,模块化只是偌大的规范中的一种,我相信很多人容易搞混淆,在此还是说明一下。
如果还是不理解,我举个例子吧:在CommonJS规范中实现了以下规范:
- ECMAScript(不同的版本支持有差异)
- 模块
- 二进制
- Buffer
- I/O流
- …
我相信你应该可以理解了,下面我会介绍一下我所学习的CommonJS模块规范。
二、规范内容
主要分为三部分:模块引用
、模块定义
、模块表示
。
2.1 模块引用
Node模块类型分为两种:
核心模块
和文件模块
,并通过require
方法来引入模块。前者是Node中内置的模块,而后者一般是用户自己定义的模块。后面提到的自定义模块也属于文件模块,只是为了区分说明。
代码如下:
// 引入`http`内置模块
const http = require('http')
// 引入文件模块
const sum = require('./sum')
// 引入第三方包`koa`,这是一个自定义模块
const koa = require(''koa)
require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。
2.2 模块定义
在CommonJS模块规范中,一个文件就是一个模块,并通过
module.exports
和exports
两种方式来导出模块中的变量或函数。
代码如下:
// 通过exports导出一个`sum`函数
exports.sum = (x, y) => x + y;
// 通过module.exports导出一个`sum` 函数
module.exports = (a, b) => a - b;
为了方便,Node为每个模块提供一个exports变量,指向module.exports。等价于:
var exports = module.exports;
如果exports导出的变量类型是引用类型如函数,则会断开与module.exports
的地址指向,导致变量导出失败。因为最终还是要靠module.exports
来导出变量的。
exports = function() {
...};
用图来表示大概就是这个样子:
同理,如果你要使用module.exports
直接导出一个对象或者函数也会重新指向新地址,而你还使用exports
导出原来地址中的变量或函数是没有用的。
// 在原来的空对象中存储一个a变量
exports.a = function() {
}
// 通过module.exports 直接导出一个引用类型变量
// 前面导出的变量失效了
module.exports = {
...}
2.3 模块标识
模块标识是
require
方法中的参数,该参数就是引入的模块文件的路径,可以没有后缀,但是必须符合小驼峰命名规范。
在上面的模块引用中,http
、./sum
、koa
就是模块标识。具体有以下几类: