您的位置:首页 > Web前端 > Node.js

nodejs模块机制

2014-07-13 00:00 134 查看
首先我们先来说说modules机制。大家对这个模块了解清楚了的话,就可以自己去翻其他插件的源代码了。

简单示例

那究竟node的模块管理是怎么实现的呢?首先我们先来看一个模块调用的示例:

loadModuleExample.js

//加载全局模块http
var http = require("http");

//加载当前目录下的duowan.js模块里面的myname
var myname = require("./duowan").myname

//加载绝对路径/node/modules下面的common.js模块
var common = require("/node/modules/common")


上面的例子里我们说明了三个事情:

通过require(moduleName)我们可以加载其它的模块

如果模块名不含有/或者或者./就会去加载通用模块,如果带有./或者../则按照相对路径规则从当前文件所在文件夹开始寻找模块,如果以/开头则从系统根目录开始找

模块引入后可以把整个模块赋予到一个变量中,也可以直接暴露模块中的某个对象或者属性或者方法。其实这点和javascript一样。

模块文件类型

node可以加载的文件类型有三种:.js, .json, .node

.js就是文本格式的javascript文件;

.json则是json格式对象,这两者(.js && .json)加载的时候被视为javascript被动态编译和处理使用;

.node则是预编译好的插件模块;

核心模块和文件模块

需要补充说明一下的是惯于上面的第二点说法中关于全局模块的说明。这个说明其实不太正确。下面我们就按照官方API的说明来正确地解释一下当你的模块名里面不带有 ./ 或者 ../ 时会怎么去找包:

首先,node会最优先加载核心模块。所谓的核心模块存在于node安装目录下的lib目录里面,他们的加载是在node容器初始化的时候就加载完成的,不需要在加载过程中进行编译,例如http这个模块。另外这些模块不会被覆盖,关于这点下面会有额外说明。

如果加载的不是核心模块,则会从当前目录寻找node_module里面的同名模块;如果当前目录没有该模块,则往上一级目录寻找node_module中的同名模块;一直找到系统根目录的node_module中有同名模块为止,没有则会抛出一个MODULE_NOT_FOUND异常。例如有以下的目录结构:

/
|_ node_module
|          |_ common.js
|_ app
|_ node_module
|           |_ http.js
|           |_ hello.js
|_ index.js


如果index.js里面require("hello.js"),则加载的将会是/app/node_module/hello.js这个文件。如果加载的是require("common.js"),则会加载/node_module/common.js这个文件。但是如果require("http"),加载的将是核心模块http,而不是/app/node_module/http.js这个文件,因为核心模块不允许被重写。

目录模块

除了上述两种模块以外,node还允许你使用目录模块。

例如有以下
7fe0
目录结构:

/ app
|_ myModules
|           |_ index.js
|_ index.js


如果我们在index.js里面引入require("./myModules"),则会加载/app/myModules/index.js。如果我们引入的是一个目录而不是一个文件,node会尝试加载这个目录下面的index.js或者index.node文件。

但是如果你在目录下定一个叫package.json文件,那么你就可以改变这一默认模块加载行为。在这个文件里面可以保存一个json对象,而里面最关键的一个属性main可以让你定义这个目录下的哪个文件是模块的主文件。例如以下的目录结构中:

/app
|_ myModules
|           |_ package.json
|           |_ myname.js
|           |_ index.js
|_ index.js


其中packages.json定义如下:

{main:"./myname.js"}


如果我们在index.js中require("./myModules"),则会加载/app/myModules/myname.js,而不是/app/myModules/index.js

当然这个package.json可不单单能够做这个事情,里面还能够定义很多像依赖管理之类的事情。由于这里是基础篇,我就先不在这里展开讨论了,等之后我们遇到相关的场景再作介绍吧。

模块缓存

对于node来说,你在主进程加载过一次的模块,就会被永久缓存起来。即使你修改过模块文件的内容,然后再在主进程尝试调用require进行重新加载,但是你会发现最后还是拿到了第一次加载的那个对象。只能通过重启node,或者通过删除保存在require.cache中的模块缓存对象,才能够重新加载模块内容。

暴露模块属性

如果我们引入一个模块,其实node就是简单地在当前环境调用某个模块文件并且解释一遍。例如下面的例子:

文件名: hello.js

console.log("Hello World!");


文件名: test.js

var hello = require("./hello");
console.log(hello);


我们执行node test你会发现直接打印出Hello World!来。而第二行则会打印出一个空对象。

但是如果我们希望暴露出模块中的一些方法或对象供外部调用者使用,那应该怎么做呢?很简单,就是利用给module.exports赋值。我们来修改一下我们的 hello.js:

文件名: hello.js

module.exports={
"name":"Justice",
"myName":function(){
return this.name;
}
}


然后再次执行node test,我们可以看到输出了一个{ name: 'Justice', myName: [Function] }对象。

值得注意的是exports的定义必须是在主进程里面完成,不可以延迟加载,即以下代码中,timeout部分无效:

setTimeout(function(){
module.exports = {"a":"Test"};
},50);


原因我猜测是因为node只会在调用require那一刹那对脚本进行解释,但是setTimeout会在主线程完成工作以后才会调用,那个时候由于module已经被编译好并且缓存下来了,所以是无法生效的。

最后提出一些小建议。大家想下如果我在一个模块中给module.exports赋值两遍会有什么后果呢?看看我们的hello.js的又一次改造版本:

文件名: hello.js

module.exports={
"name":"Justice",
"myName":function(){
return this.name;
}
}

module.exports={"name":"Moc"}


无容置疑,最后模块导出的就只有{"name":"Moc"}这个对象了。所以如果大家要调用module.exports,最好放到你的文件尾部来调用,以防止这种覆盖问题。

module的其它API

module的API还有以下的属性:

module.id = 模块的ID,通常是当前模块文件路径,含文件名

module.filename = 当前模块文件路径,含文件名

module.loaded = 判断模块当前是否已加载

module.parent = 加载当前脚本的模块对象。

module.children = 当前模块加载的模块对象集合,是一个数组

官方网址:http://www.nodejs.org/api/modules.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  nodejs modules