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模块
浙公网安备 33010602011771号