node.js require 实现机制初窥;pomelo代码分析5----------- pomel-loader模块

这里接上面一章,blablabal


这篇主要介绍node.js的模块加载机制和代码分析。顺带提一下pomelo-loader。


下面我们来介绍pomelo的loader模块,

npmjs.org上面介绍如下:

pomelo中使用Convention over Configuration的形式管理工程目录,不同的功能按约定放在不同的目录下。pomelo-loader为pomelo提供了按目录加载模块的功能。


代码如下:

129 var requireUncached = function(module){
130     delete require.cache[require.resolve(module)]
131     return require(module)                                                                                                                                 
132 }

从这个模块的目录可以看到,实际上pomelo-loader是完全独立的模块不依赖于其他模块,而且lib下面只有一个js文件。而且这个js才132行。


基本功能就是扫描某个目录,看后缀名,最终调用require来循环加载相应文件而已。






我们真正要看的才刚刚开始:

try 大家已经配置好调试环境 ; 

exception  可以看看我前面介绍node-inspector的ZZ文章。

final Node-inspector + chrome-debuger真是太tm好用了!! 牛!


我们用pomelo-rpc这个模块下面的server.js这个程序来启动调试的,大家完全可以随便弄个文件,只要它调用pomelo-loader的loader()函数即可。或者自己写2行代码的js来测试。太基础,就不展开了。有问题的童鞋直接M我吧。


OK,这里假设大家已经break到130行。并且Node-inspector一切运作正常。


这个时候大家鼠标放到require.cache 这个变量上,可以看到这个Object的内容,在我这里有几十条。这些都是当前上下文已经加载的module。

我们在把这个Object打印出来可以看到都是我们工程里面对应的js文件,也包括各个子module的。(很长很长,我剪切一部分出来看)

/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/index.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/lib/rpc-client/client.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/lib/rpc-client/mailbox.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/lib/rpc-client/mailboxes/blackhole.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/lib/rpc-client/mailboxes/ws-mailbox.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/lib/rpc-client/mailstation.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/lib/rpc-client/router.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/lib/rpc-server/acceptor.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/lib/rpc-server/acceptors/tcp-acceptor.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/lib/rpc-server/acceptors/ws-acceptor.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/pomelo-logger/node_modules/log4js/lib/connect-logger.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/pomelo-logger/node_modules/log4js/lib/date_format.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/pomelo-logger/node_modules/log4js/lib/layouts.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/pomelo-logger/node_modules/log4js/lib/levels.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/pomelo-logger/node_modules/log4js/lib/log4js.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/pomelo-logger/node_modules/log4js/lib/logger.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/socket.io-client/bin/builder.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/socket.io-client/lib/events.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/socket.io-client/lib/io.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/socket.io-client/lib/json.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/socket.io-client/lib/namespace.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/socket.io-client/lib/parser.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/socket.io-client/lib/socket.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/socket.io-client/lib/transport.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/socket.io-client/lib/transports/websocket.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/socket.io-client/lib/transports/xhr-polling.js: Module
/home/chenee/myCode/NodeJS/test/node_modules/pomelo-rpc/node_modules/socket.io-client/lib/transports/xhr.js: Module


我们step into这个调用会跑到一个叫做Module.js的文件里面。这个文件在chrome-debuger左边的Sources目录里面看不到它所在的目录,只能看到是处于顶层的。

其实这个就是Node的源码lib中间的文件,

node-v0.10.24/lib/module.js


  1. ok,这个文件大概500行,东西不多,其实就是Module这个类的实现:包括操作和记录而已。
  2. 这个文件看一遍,我们就对Node.js的module,export{},这些基本概念有了深入和透彻的理解。所以很值得看。

另外,@朴灵 已经在他的大作:
深入浅出Node.js(三):深入Node.js的模块机制 http://www.infoq.com/cn/articles/nodejs-module-mechanism
里面详细的介绍了require的JS层面的的module加载逻辑。

(无非就是判断目录,后缀名,一些其它细节,最后找到对应文件加载。加载成功以后放到module.cache{}里面留着备用。。。
这个其实和lua的实现或者其他什么什么的模块加载思路都不会有什么太大的差别,大家看过一个,便可以一通百通)

这里要补充的是:
1、
大家如果仔细的跟踪一遍流程会发现实际上load模块的操作是在
374 Module.prototype._compile = function(content, filename) {                                                   
.......
439   var compiledWrapper = runInThisContext(wrapper, filename, true);
.......
}


 这个函数实现的。而这个函数对应于 node-v0.10.24/src/node_script.cc 这个C++代码。

而这里的runInThisContext实际上是一个C++的Template。
最终调用的是VM来实现,也就是V8 engine来执行我们的对应的模块JS文件,并且把相应的函数、变量导入到VM的上下文。最终完成一个模块
从文本变成内存中Object的过程。


这里的V8 其实也就相当有LUA里面的虚拟机的概念,早先虽然看过LUA的实现,但是已经完全木有什么印象了,所以无力展开(如果有兴趣可以等我填前面的V8 Engine的坑吧)

2、
回到module.js文件中
455  var args = [self.exports, require, self, filename, dirname];
456  return compiledWrapper.apply(self.exports, args);


在这句断点,然后跟进去会发现,到了我们的module文件里面。而且我们的模块文件已经被封装了一下。实际上内容如下:
(function (exports, require, module, __filename, __dirname) {

//原文件内容

};)

可以看出原先困惑我们的exports是在这里实现的。

比如,
1、我们原先困惑,module这个Object到底是什么东西???为啥在一个JS文件里面不用初始化就能够直接使用??
2、module.exports,exports到底是什么?? 有什么关系??

整理一下:
原来,我们require一个文件A的时候,是由系统module = new Module(),并根据A的一些信息初始化这个module。
然后把这个module的exports当成参数调用Javascript的模块文件A。
self.exports 就是module.exports 所以,我们能够在A中通过赋值exports或者module.exports来暴露本模块的变量和函数!!

OK真相大白,还不白的多看2眼代码,尤其是这个函数。
374 Module.prototype._compile = function(content, filename) { 

以后再有人迷惑,或者忽悠exports,

可以先扇他一耳光,再对着他吼

“TMD不就是lib/modules里面的_compile()函数实现的几行代码吗,有毛线啊!!!balblablalblablabl”。













郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。