深入浅出nodejs:第二章----模块机制

Javascript先天就缺乏的一项功能:模块.通过<script>引入的代码显得杂乱无章,语言自身毫无组织和约束能力.人们不得不用命名空间等方式认为的约束代码,以求达到安全和易用的目的.但是看似凌乱的javascript编程现状不代表着社区没有进步,javascript的本地化编程之路一直在探索中.社区也为javascript制定了相应规范,其中commonJS规范的提出算是最为重要的里程碑.

2.1:CommonJs规范

CommonJs规范为Javascript制定了一个美好的愿景----希望javascript能够在任何地方运行.

2.1.1:CommonJs的出发点

后端javascript的规范远远落后.对于JS自身而言,它的规范依然薄弱,还有以下缺陷:

  1.没有模块系统;

  2.标准库较少.仅定义了部分核心库,对于文件系统,I/O流等常见需求却没有标准的API.

  3.没有标准接口.几乎诶呦定义过如web服务器或者数据库之类的标准统一接口.

  4.缺乏包管理系统.导致JS应用中基本没有自动加载和安装依赖的能力.

CommonJs的提出主要是为了弥补当前js没有标准的缺陷,具备大型应用的基础能力,而不是停留在小脚本程序的阶段,他们期望那些用CommonJS写出的应用可以具备跨越宿主环境执行的能力,可以编写以下应用:

  1.服务器端JS应用程序;

  2.命令行工具;

  3.桌面图形界面应用程序;

  4.混合应用.

Node能以成熟的姿态出现,离不开CommonJs规范的影响.在服务器端,CommonJs能以寻常的姿态写进公司的项目代码中,离不开Node优异的表现.

2.1.2:CommonJs的模块规范

CommonJs对模块的定义十分简单,主要分为模块引用,模块定义和模块标示三个部分.

1.模块引用

模块引用的示例代码如下:

var math = require('math');

在CommonJs规范中,存在require()方法,这个方法接受模块标识,以此引入一个模块的API到当前上下文中.

2.模块定义

在模块中,上下文提供require()方法来引入外部模块,对应引入的功能,上下文提供了exports对象用于导出当前模块的方法或者变量,并且它是唯一导出的出口.在模块中,还存在一个module对象,它代表模块自身,而exports是module的属性.在Node中,一个文件就是一个模块,将方法挂载在exports对象上作为属性即可定义导出的方式.

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

在另一个文件中,我们通过require()方法引入模块后,就能调用定义的属性和方法了:

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

3.模块标识

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

模块的定义十分简单,接口也十分简洁.它的意义在于将类聚的方法和变量等限定在私有的作用域中,同时支持引入和导出功能以顺畅的连接上下游依赖.每个模块具有独立的空间,互不干扰,引用时也显得干净利落.

CommonJs构建的这套模块到处和引入机制使得用户完全不必考虑变量污染,命名空间等方案与之相比相形见拙.

2.2:NodeJs模块实现

node在实现中并非完全按照规范实现,而是对模块规范进行了一定的取舍,同时也增加了少许自身需要的特性,尽管规范中exports require和module听起来十分简单,但是Node在实现它们的过程中究竟经历了什么,需要知晓这个过程.

Node引入模块,需要经历三个步骤:

  1.路径分析

  2.文件定位

  3.编译执行

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

核心模块部分在node源码的编译过程中,编译进了二进制执行文件,在Node进程启动时,部分核心模块就被直接加载进内存中,所以这部分核心模块引入时,文件定位和编译执行这两个步骤可以省略,并且在路径分析中优先判断,所以它的加载速度是最快的.

文件模块则是在运行时动态加载,需要完整的路径分析,文件定位,编译执行过程,速度比核心模块慢.

2.2.1:优先从缓存加载

我们需要知晓的一点事,与前端浏览器会缓存静态脚本文件以提高性能一样,Node对引入过的模块都会进行缓存,以减少二次引入时的开销.不同的是,浏览器仅仅缓存文件,而Node缓存的是编译和执行之后的对象.

不论是核心模块还是文件模块,require()方法对相同模块的二次加载都一律采用缓存优先的方式,这是第一优先级的.不同之处在于核心模块的缓存检查先于文件模块的缓存检查.

2.2.2:路径分析和文件定位

因为标示符有几种形式,对于不同的标识符,模块的查找和定位有不同程度的差异.

  1.模块标识符分析

        前面提到过,require()方法接受一个标识符作为参数.在Node实现中,正是基于这样一个标识符进行模块查找的.模块标识符在Node中主要分为以下几类:

        ●核心模块,如http/fs/path : 优先级仅次于缓存加载,它在Node的源代码编译过程中已经编译为二进制代码,其加载过程最快.

        如果试图加载一个与核心模块标识符

                  ●..或..开始的相对路径文件模块   

                      ●以/开始的绝对路径文件模块

          ●非路径形式的文件模块,如自定义的connect模块

 

posted on 2015-07-16 10:52  谷子多多  阅读(901)  评论(0)    收藏  举报