[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);
  }

从上可以看出,执行过程为:

  1. 新建Module对象
  2. 查找要Load的Module文件
  3. 载入文件内容并进行解析,同时添加该Module的上下文
  4. 返回这个模块的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

posted @ 2012-05-28 00:13  MySirius  阅读(3019)  评论(0编辑  收藏  举报