Email:longsu2010 at yeah dot net
node的模块系统简洁明了,文件和模块一对一,例如var conf = require(".conf.js");为加载当前目录下的conf.js文件,并将返回值赋值为conf变量,假设conf.js内容如下:
conf.js:
var key = "the key";
exports.key = key;
在conf.js中声明的局部变量key将会成为私有变量,该模块暴露一个key属性,可以通过require(".conf.js").key引用。
模块缓存机制:
当模块第一次加载的时候模块将会被缓存,也就是说调用多次require("./a.js")实际返回的是同一个对象,a.js中的代码不会被多次执行。
模块在require.cache中以键值对的方式缓存,键是模块的绝对路径,根据require.resolve方法获取。
循环依赖问题:
看一个例子
a.js
console.log("a start");
exports.isDone = false;
var b = require("./b.js");
console.log("b.isDone in a", b.isDone);
exports.b = b;
exports.isDone = true;
console.log("a end");
b.js
console.log("b start");
exports.isDone = false;
var a = require("./a.js");
console.log("a.isDone in b", a.isDone);
exports.a = a;
exports.isDone = true;
console.log("b end");
main.js
var a = require("./a.js");
var b = require("./b.js");
console.log(a === b.a);
console.log(b === a.b);
执行main.js输出结果为:
a start
b start
a.isDone in b false
b end
b.isDone in a true
a end
true
true
加载过程分析:
1、将main缓存在require.cache中。
2、执行main中的require("./a.js")时首先检测a.js是否已经缓存,发现并未缓存,加载并执行a.js,并将a缓存。
3、执行a.js中的require("./b.js")时首先检测b.js是否已经缓存,发现并未缓存,加载并执行b.js,并将b缓存
4、执行b.js中的require("./a.js")时首先检测a.js是否已经缓存,发现已经缓存,直接从require.cache获取a的exports对象(此时a的exports对象中的isDone还是false,代码没执行完嘛)。
5、b.js执行完成,返回到a.js中继续执行,a.js中将a的exports对象中的isDone设置为true,a.js执行结束。
6、执行main中的require("./b.js"),同样先检查b.js是否已经缓存,发现已经缓存,所以从reqiure.cache中获取b,b.js代码不会再次执行。
7、执行main的其他代码。
总结:
1、有时候顺序很重要,谨记。
2、默认情况下一个模块只加载并执行一次,若强制重新加载可以将模块从缓存中删除。例:delete require.cache[require.resolve("./b.js")];
3、使用require加载一个已经加载过的模块其实是变量赋值。
核心模块:
node中有一些模块被编译成二进制,他们的定义可以在node源文件的lib目录中找到。核心模块拥有者更高的优先级,如require("http")总是加载核心模块中的http模块。
文件模块:
如果没有明确的指定加载的模块,如require("./main"),node将尝试加载./main.js(js模块),若不成功则尝试加载./main.json(需要JSON结构),如不成功则尝试加载./main.node(被认为是编译的插件)。
如果模块以“/”开头,那么说明使用绝对路径加载模块。如果模块以“./”或者“../”开头,那么说明使用相对路径加载模块。如果模块不以“/” “./” “../”开头,并且该模块也不是核心模块,那么在当前路径下的node_modules路径下加载模块,如果加载不到那么在当前目录的上级目录中的node_modules中加载,依此类推,直到根目录。
如果要加载的模块不存在node将抛出异常。
文件夹模块:
使用独立的目录来组织程序和类库并提供一个唯一的访问入口是不错的注意,如下介绍如何将一个目录作为一个模块。
1、首先创建一个目录,假设为jsutil。
2、在该目录下创建一个名为package.json的文件。
3、在package.json中数据如下内容,关键点是main配置项。
{"name" : "jsutil", "main" : "./lib/jsutil.js"}
4、在jsutil中创建lib/jsutil.js文件。
此时执行require("./jsutil")将会加载./jsutil/lib/jsutil.js
另外如果不创建package.json文件,node将一次试图加载jsutil目录下的index.js和idnex.node文件。
模块的module对象:
在每一个模块中都有一个module变量,该变量是指向当前模块的引用,其中的exports属性是exports变量的一个引用。
module.exports:由node模块系统创建,是require的返回值。有些时候我们希望require返回的是我们自己创建的对象,那么可以如下这样做:module.exports = new MyFunc(){};
module.require:实现的功能与require类似。
module.id:模块的唯一标识,典型的应用是模块的绝对路径。
module.filename: 模块的带路径文件名(绝对路径)。
module.loaded:模块加载是否结束。
module.parent:那个模块加载的当前模块。
module.children:当前模块加载了那些模块。
获取主模块:
主模块是程序某一次执行的入口模块,即执行哪个模块启动的当前进程。如node main.js,main就是主模块。
会写python的都知道if "__main__" == __name__ : pass 这段代码的含义,与此类似require.main记录了那个模块是主模块。所以判断当期那模块是否是主模块可以这样写 if( require.main === module) {/* do something */}