Node -- 模块机制

来源:互联网 发布:刘邦历史书籍 知乎 编辑:程序博客网 时间:2024/05/29 17:49

JavaScript从诞生以来,就缺乏一项功能:模块。不像其他高级语言,Java有类,python有import机制,JS只能通过script标签来引入代码,显得杂乱无章。

在Node出现之前,服务器端JS基本没有市场。但是经历十多年的发展后,社区也为JS制定了相应的规范,其中CommonJS规范的提出算是最为重要的里程碑。

CommonJS规范

CommonJS规范为JS制定了一个美好的愿景:希望JS能够在任何地方运行——CommonJS规范的提出,主要是为了弥补当前JS没有标准的缺陷,以达到像Python Java具备开发大型应用的基础能力,而不是停留在脚本程序的阶段。他们希望那些用CommonJS API写出的应用可以具备跨宿主环境执行的能力。这样不仅可以利用JS开发客户端应用,还可以编写:
1.服务器端JS程序
2.命令行工具
3.桌面图形界面应用程序

这里写图片描述

上图是Node,W3C,CommonJS,ECMAScript之间的关系。

Node借鉴CommonJS的Module规范实现了一套非常易用的模块系统。NPM对Packages规范的完好支持使得Node应用在开发过程中事半功倍。

CommonJS的模块规范

CommonJS对模块的定义时分简单,主要分为模块引用,模块定义和模块标识三个部分。

1、模块引用

var math = require('math');

2、模块定义

在Node中,一个文件就是一个模块,将方法或者变量挂载在export对象上作为属性即可定义到出的方式。export是module的属性。

// math.jsexports.add = function () {    var sum=0, i=0,        args = arguments,        l = args.length;     while(i<l){        sum += args[i++];     }    return sum; };

在另一个文件中,通过require方法引入模块后,就可以定义属性或方法了:

// program.jsvar math = require('math'); exports.increment = function (val) {    return math.add(val, 1); };

3、模块标识

模块标识其事就是传递给require方法的参数,它必须是符合小驼峰命名的字符串,或者以. 、.. 开头的相对路径或者绝对路径。可以没有文件名后缀.js。

CommonJS的这套模块导出和引入机制,使得用户完全不必考虑变量污染。

Node的模块实现

Node的模块分为两类,一类是Node提供的模块,称为核心模块;一类是用户编写的模块,称为文件模块

核心模块部分在Node源码的编译过程中,编译进了二进制执行文件,在Node进程启动时,部分核心模块就被直接加载进内存中。
而文件模块则是在运行时动态加载。它需要完成完整的如下三个步骤:

在Node中引入模块,需要经历的三个步骤:
1、路径分析(Node在路径分析时,将会优先判断核心模块)
2、文件定位
3、编译执行

模块加载详述:

1、优先从缓存加载

Node和前端浏览器会缓存静态脚本文件一样, 也会对引入的模块进行缓存。并且,Node缓存的是变异和执行后的对象,而不是像浏览器那样仅缓存文件。

不论是核心还是文件模块,require方法对相同模块的二次加载都一律次啊用缓存优先的方式。核心模块的缓存检查会先于文件模块的缓存检查。

2、路径分析和文件定位

3、模块编译

在Node中,每个文件模块都是一个对象。它的定义如下:

function Module(id, parent) {     this.id = id;    this.exports = {};     this.parent = parent;    if (parent && parent.children) {         parent.children.push(this);    }    this.filename = null;     this.loaded = false;      this.children = [];}

编译和执行是引入文件模块的最后一个阶段。定位到具体的文件后,Node会新建一个模块对象,然后根据路径载入并编译。编译成功的模块会想起文件路径作为索引缓存在Module._cache对象上,以提高二次引入的特性。

模块调用栈

各种模块之间的调用关系:

这里写图片描述

包与NPM

在模块之外,包和NPM则是将模块联系起来的一种机制。包的出现,在模块的基础上进一步组织了JS代码。

在介绍NPM之前,首先介绍Common JS的包规范。

这里写图片描述

CommonJS的包规范定义 由包结构和包描述文件两个部分组成,前者用于组织包中的各种文件,后者则用于描述包的相关信息。

1、包结构

包实际上是一个存档文件,即一个目录直接打包为.zip格式的文件,安装后解压还原为目录。

完全符合CommonJS规范的包目录应该包含如下文件:
1、package.json:包描述文件
2、bin:用于存放可执行二进制文件的目录
3、lib:用于存放JS代码的目录
4、doc:用于存放文档的目录
5、test:用于存放单元测试用例的代码

2、包描述文件与NPM

package.json即包描述文件,它是包的重要组成部分,NPM的所有行为都与包描述文件的字段息息相关。

CommonJS为package.json文件定义了一些必需字段如name, description, version等,还有一些可选字段如homepage,os等。

包规范的定义可以帮助Node解决依赖包安装的问题,而NPM正是基于该规范进行了实现。

3、NPM常用功能

CommonJS包规范是理论,而NPM是其中一种实践。对于Node而言,NPM帮助完成了第三方模块的发布、安装和依赖等。借助NPM,Node与第三方模块之间形成了很好的一个生态系统。

借助NPM,可以帮助用户快速安装和管理依赖包。

ADM和CDM

ADM和CDM规范都应用于前端场景。

ADM规范是CommonJS模块规范的一个延伸。它的模块定义如下:

define(id?, dependencies?, factory);

ADM模块需要用define来明确定义一个模块,而在Node实现中是隐式包装的。它们的目的是进行作用域隔离。

ADM需要在生命模块的时候制定所有的依赖,并通过形参传递依赖到模块中。

define(['dep1', 'dep2'], function (dep1, dep2) {            return function () {};});

与ADM相比,CDM模块更接近于Node对CommonJS规范的定义:

define(factory);

在依赖部分,CDM支持动态引入:

define(function(require, exports, module) {    // The module code goes here});

require, exports, module通过形参传递给模块,在需要依赖模块时,随时调用require引入即可。

原创粉丝点击