[Node.js] Module.Require机制研究
最近开始用Node.js写Server端的脚本,由于原来一直在做的.Net中开发理念的影响,在设计上和代码实现上尽可能地进行封装。
Node.js中跟.Net中对象最相近的就是Module(模块)了,但渐渐地发现两者的差距其实很大。
刚开始我写了一个Module,发现怎么都不对,代码大概是:
var fs = require('fs'); var data = fs.readFileSync('...'); fs.watchFile('...', function(curr, prev) { if(curr.mtime - prev.mtime) { reloadFileData(); } }); exports.data = data;
假设这个为filedata.js模块,之后在其它模块中require:var fd = require('filedata');
结果发现在文件更新update data对象值之后,外面应用的data值一直没变。
到底是什么原因导致这种直接对参数进行exports的方法失效了呢?会不会是在require的过程中进行了一些与我们想象中不同的操作呢?
查阅了相关文档,对node.js的require机制进行了一定了解:
1. 模块查找路径
Node.js的模块载入查找路径是由NODE_PATH这个全局环境变量保存,默认为
$HOME/.node_modules $HOME/.node_libraries $PREFIX/lib/node
即在Home文件夹下的.node_modules和.node_libraries文件夹和安装时指定的$PREFIX下的lib/node文件夹下查找。
此外,优先级最高的是脚本执行当前目录下的node_modules文件夹下的模块。
所以在写Node脚本时尽量少使用相对或者绝对路径来load模块,可将模块放入node_modules文件夹下,直接require('***')就可以了。
2. require中的执行过程
查看了node的module.js源码,require调用的是_load函数,_load函数中最关键的下面几句:
if (NativeModule.exists(filename)) { // REPL is a special case, because it needs the real require. if (filename == 'repl') { var replModule = new Module('repl'); replModule._compile(NativeModule.getSource('repl'), 'repl.js'); NativeModule._cache.repl = replModule; return replModule.exports; } debug('load native module ' + request); return NativeModule.require(filename); }
从上可以看出,执行过程为:
- 新建Module对象
- 查找要Load的Module文件
- 载入文件内容并进行解析,同时添加该Module的上下文
- 返回这个模块的exports对象,这个对象包含了这个Module的所有公开接口
3. Cache机制
Node.js在每次第一次Load Module时就会将该Module缓存,存入全局的_cache中。
此后若再require该Module,就直接返回在_cache中的对象,不再重新读取脚本文件和构建上下文。
可以从以下代码中看到Cache机制的代码实现:
var cachedModule = Module._cache[filename]; if (cachedModule) { return cachedModule.exports; }
为了验证是否如此,写了个简单的Number对象,实现Number对象的get和set方法并exports,然后运行如下:
> var aNumber = require('./Number'); undefined > aNumber.get(); {} > aNumber.get().toString(); '-1' > var bNumber = require('./Number'); undefined > aNumber.set(10); undefined > aNumber.get(); 10 > bNumber.get(); 10
Number模块中Number对象的默认值为-1。
可以看出aNumber和bNumber指向的是同一对象,重复的require并不会导致不同Module Instance的存在。
当然,也可以在require前强制删除_cache中的对应key-value,使对该Module的cache失效,以重新Load脚本文件,不过不推荐。
对require机制有一定了解之后,能完整地重现之前问题的运行过程,但是,理论上应该还是会正常地访问到模块内的string参数,
require的js代码中并没有对非function类型的exports中的对象进行特殊处理,理论上module.exports中保存的还应该是这个string参数对象。
接下来继续研究node的相关源代码,发现了更有趣的东西,在下篇中与大家继续分享。
参考资料:
http://visionmedia.github.com/masteringnode/book.html
https://github.com/joyent/node/blob/master/lib/module.js
http://jherdman.github.com/2010-04-05/understanding-nodejs-require.html