温习Node基础(1)——模块
字数:2702
作者:Jerry
了解Node的人一定都知道NPM——node的包管理器。NPM是基于CommonJS定义的包规范,实现了依赖管理和模块的自动安装等功能。
本篇文章从简单的模块定义与使用开始,逐步讲解Node中模块怎么定义,怎么require模块,模块require的机制等等。讲的东西很浅显,但却很重要。
简单模块的定义和使用
以一个例子开始:
新建文件夹SimpleModule,进入该文件夹,新建两个文件:
circle.js
和app.js
。即如:
----SimpleModule |----app.js |----circle.js
circle.js内容:
var PI = Math.PI; exports.area = function(r){ return PI * r * r; }; exports.circumference = function(r){ return 2 * PI *r; }
circle.js就是简单的模块,功能比较简单,就是计算圆周和圆面积。
app.js:
// 引入circle.js var circle = require('./circle.js'); console.log('Circle area (radius=4): '+circle.area(4));
可以看到模块调用很方便,require模块所对应的文件即可。
可以看到,Node中模块的定义与调用均很简单。
注意:
模块可以暴露方法。这个例子中,通过把函数添加到exports对象上,完成了方法的暴露,外部可通过
exports.functionName(param)
来调用方法。模块也可以暴露对象。暴露方法时,我们是为exports对象添加属性,而暴露对象时,直接为exports赋值即可:
module.exports=ObjectX;
模块载入策略
上面的例子中,我们通过require来载入了自己定义的模块,接下来将系统的了解模块的载入策略。
原生模块和文件模块
Nodejs的模块分为两类:原生(核心)模块和文件模块:
- 原生模块:在nodejs源码编译时编译进了二进制执行文件,加载速度快。
- 文件模块:动态加载,加载速度比原生的慢。
node对原生模块和文件模块都进行了缓存,所以第二次require时,不会有重复开销。
文件模块分为3类
以后缀区分,文件模块有分为3类,nodejs会根据后缀名来决定加载方法。
- .js——通过fs模块同步读取js文件并编译执行。
- .node——C/C++编写的Addon,通过dlopen方法加载。
- .json——读取文件,调用JSON.parse解析加载。
具体的加载方法我们暂时不去深入,我们只要知道,加载方法加载module后,返回module的exports对象。像我们的circle.js
模块,加载后暴露了exports对象,因此我们可以调用exports.area
和exports.circumference
方法。
require方法的文件查找策略
Node有4类模块(原生模块和3种文件模块),加载优先级各不相同。
模块加载顺序:
从文件模块缓存中加载。
require时,首先查看文件模块缓存,如果缓存中存在,直接返回exports对象。
其次从原生模块加载。
原生模块也有缓存,同样优先从缓存区加载。如果缓存区没有被加载过,调用原生模块的加载方式进行加载和执行。
从文件加载。
当文件模块缓存中不存在,而且也不是原生模块时,Node会解析require方法传入的参数,并从文件系统中加载实际的文件。
require方法接受4种参数:
- http、fs、path等,原生模块。
./mod
或../mod
,相对路径的文件模块。- /path/mod,绝对路径的文件模块。
- mod,非原生模块的文件模块。
包结构
Javascript本身是没有包结构的,CommonJS定义了包结构的规范(http://wiki.commonjs.org/wiki/Packages/1.0)。
一个符合CommonJS规范的包应该是如下结构:
- 一个package.json文件在包顶级目录下。
- 二进制文件在bin目录下。
- javascript代码在lib目录下。
- 文档在doc目录下。
- 单元测试在test目录下。
package.json定义了一系列必须字段,以下只讲几个主要的:
- name,包名。在NPM上唯一,不能带有空格。
- description,包简介。
- version,版本号。
- keywords,关键字组。用于NPM中的分类搜索。
- ...
如果你的包符合CommonJS规范,那么你可以通过NPM发布你的包。
npm publish <folder>
Nodejs模块与前端模块的异同
通常一些模块可以同时适用于前后端,但是浏览器通过<script></script>
标签载入js文件的方式与node不同。
node在载入到最终执行过程中,进行了包装,使得每个js文件中的变量天然地形成在一个闭包中,不会污染全局变量。
来看jQuery源码的最后一段来加深对这种不同之处的理解:
(function( window, undefined ) {
// 大量代码
...
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
// CommonJS规范
module.exports = jQuery;
} else {
// AMD规范
if ( typeof define === "function" && define.amd ) {
define( "jquery", [], function () { return jQuery; } );
}
}
if ( typeof window === "object" && typeof window.document === "object" ) {
window.jQuery = window.$ = jQuery;
}
})( window );
详细从jQuery源码可以看出Nodejs模块与前端模块的差别之处了。前端模块下,jQuery通过为window全局对象添加属性完成暴露,而Nodejs下则是为module.exports赋值完成暴露。