JavaScript的模块机制—CommonJS规范

背景 CommonJS规范 CommonJS模块规范 Node的模块实现

背景

JavaScript早期的"script"标签的代码引入方式显得杂乱无章,缺乏规范,不像Java有类文件,python有import机制,Ruby有require,PHP有include和require。

早期JavaScript的规范主要是ECMAScript,ECMAScript它主要包含词法、类型、上下文、表达式、声明、方法、对象等语言的基本要素。有如下缺陷:

没有模块系统 标准库较少,没有I/O流 没有标准接口,如数据库之类的统一接口 缺乏包管理系统

经过后期的发展,JavaScript不断被类聚和抽象,终于有社区为JavaScript制定了规范,其中一个著名的规范就是CommonJS规范。

CommonJS规范

CommonJS的提出为JavaScript覆盖如下内容的规范:

模块、二进制、Buffer、字符集编码、I/O流 进程环境、文件系统、套接字、单元测试、Web服务器网关接口、包管理 等

alt

Node借鉴CommonJS的Modules实现了一套非常易用的模块系统,NPM借鉴Packages规范。

CommonJS模块规范

CommonJS模块规范主要包括:模块引用、模块定义和模块标识。

1、 模块引用

var math = require('math');
2、模块定义
// math.js
exports.add = function(){
    var sum = 0, i = 0, args = arguments, l =args.length;
    while(i < l){
        sum += args[i++];
    }
    return sum;
};
// program.js
var math = require('math');
exports.increment = function (val) {
    return math.add(val, 1);
};

3、模块标识

模块标识就是给require()方法的参数,它必须是符号小驼峰命名的字符串,或者以.、..开头的相对路径,或者绝对路径。

每个模块具有独立的空间,互不干扰。

alt

用户不用考虑变量污染,命名空间等方案与之相比相形见绌。

命名空间是为了防止不同人编写类库发生命名冲突而设计的,使变量、函数名称、类名称在本空间和在其他空间都可以使用相同的名称。就好比不同的文件夹下可以有相同的文件名一样,但在相同的文件夹下不能有重复的文件名,命名空间就好比这个虚拟的文件夹。

一个是水果的table

<fruit:table>
<fruit:tr>
<fruit:td>Pear</h:td>
<fruit:td>Apple</h:td>
</fruit:tr>
</fruit:table>

一个是桌子的table:

<wood:table>
<wood:name>Desk</f:name>
<wood:width>110</f:width>
<wood:length>90</f:length>
</wood:table>

Node的模块实现

在Node中引入模块,有如下3个步骤:

路径分析 文件定位 编译执行

Node中的模块分为两类:核心模块(Node提供的模块,如http、fs、path)和文件模块(用户编写的模块)。

核心模块在Node源代码的编译过程中,直接编译进了二进制文件。在Node进程启动时,部分核心模块就被直接加载进内存中,所以这部分核心模块引入时,稳健定位和编译执行两个步骤省略掉了,并且在路径分析中有限判断,所以加载速度最快。 文件模块则是在运行时动态加载,需要完整的路径分析、文件定位、编译执行的过程,速度比核心模块慢。

Node对引入过得模块会进行缓存,缓存的是编译和执行之后的对象。

require方法对相同模块的二次加载一律采用缓存优先的方式。

模块路径是按照:

当前文件目录下的node_modules目录 父目录下的node_modules目录 逐级向上,直到跟目录下的node_modules目录

文件不同的扩展名,处理方式不同:

.js文件。通过fs模块同步读取文件后编译执行 .node文件。这是C/C++编写的扩展文件,通过dlopen()方法加载最后编译生成的文件。 .json文件。通过fs模块同步读取文件后,用JSON.parse()解析返回的结果。 其余扩展名文件当做.js文件处理。

我们知道每个模块文件中存在require、exports、module这3个变量,但它们在模块文件中并没有定义,那么从何而来?另外每个模块中还有_filename、_dirname这两个变量。

事实上,在编译的过程中,Node对获取的JavaScript文件内容进行了头尾包装。在头部增加了

(function(exports, require, module, _filename, _dirname){

})

这样每个模块文件之间都进行了作用域的隔离。

文章来源:

Author:大官人
link:https://www.daguanren.cc/post/JavaScript_module_commonjs.html