在上一篇文章里,我大概解释了node的代码组织方式,有三个关键词: module, exports, require,这里我根据朴灵的深入浅出nodejs详细解释一下这三个关键词。
在node引入模块并执行的过程中发生了什么事情的呢?
1. 路径分析
2. 文件定位
3. 编译执行
在node中,模块包括了两种,一种是核心模块,在node环境启动的时候,加载到内存中,大家都知道的,内存读取起来很快,另外一种是用户自己编辑的js文件模块,它是在执行的时候动态加载进去了,简单的说就是如果写了一个my_util.js模块,如果不通过require引入它,它就在那,不多不少,不会消失,也不能使用,require时,要进行路径分析,文件定位,编译执行后加载到内存里才能使用,也就是自己定义的文件,效率肯定没有核心模块效率高。
每个js文件在引入的时候都有个module对象,我们写两个文件:
util.js
function getSplitArray(originString, splitString){
return originString.split(splitString);
}
module.exports = {
getSplitArray: getSplitArray
};
console.log(module);
main.js
var util = require('./util.js');
console.log(module);
一个exports一个方法,一个使用它,打印一下每个文件的module对象console.log(module),输出:
Module {
id: '/home/chenyafei/workspace/node/study/util.js',
exports: { getSplitArray: [Function: getSplitArray] },
parent:
Module {
id: '.',
exports: {},
parent: null,
filename: '/home/chenyafei/workspace/node/study/main.js',
loaded: false,
children: [ [Circular] ],
paths:
[ '/home/chenyafei/workspace/node/study/node_modules',
'/home/chenyafei/workspace/node/node_modules',
'/home/chenyafei/workspace/node_modules',
'/home/chenyafei/node_modules',
'/home/node_modules',
'/node_modules' ] },
filename: '/home/chenyafei/workspace/node/study/util.js',
loaded: false,
children: [],
paths:
[ '/home/chenyafei/workspace/node/study/node_modules',
'/home/chenyafei/workspace/node/node_modules',
'/home/chenyafei/workspace/node_modules',
'/home/chenyafei/node_modules',
'/home/node_modules',
'/node_modules' ] }
//
Module {
id: '.',
exports: {},
parent: null,
filename: '/home/chenyafei/workspace/node/study/main.js',
loaded: false,
children:
[ Module {
id: '/home/chenyafei/workspace/node/study/util.js',
exports: [Object],
parent: [Circular],
filename: '/home/chenyafei/workspace/node/study/util.js',
loaded: true,
children: [],
paths: [Object] } ],
paths:
[ '/home/chenyafei/workspace/node/study/node_modules',
'/home/chenyafei/workspace/node/node_modules',
'/home/chenyafei/workspace/node_modules',
'/home/chenyafei/node_modules',
'/home/node_modules',
'/node_modules' ] }
module有id, exports filename, loaded, children, paths属性,有经验的开发人员很快就明白了module执行的代码组织结构,id模块文件的唯一标示,实际上是文件的全路径,exports是对外开发的属性,filename是文件名,loaded没懂有什么用,children代表子模块,我的理解是可以通过children找到所需要的子模块,下面详细说下paths。
这个和require查找策略有关,比如我们的例子里require('./util.js'),如果我们改为require('util')会出现什么情况呢:
会查找到核心模块里的util模块,也就是说,require是先从核心模块查找,也就是从内存中找,如果查找到,就停止查找,查找不到从当前路径下找,顺序依次是:
<pre name="code" class="javascript">/home/chenyafei/workspace/node/study/node_modules',
'/home/chenyafei/workspace/node/node_modules',
'/home/chenyafei/workspace/node_modules',
'/home/chenyafei/node_modules',
'/home/node_modules',
'/node_modules'
所以说require是现从当前目录查找,然后从父目录,其次是父目录的父目录,直到查找到根目录中,可以看出来,文件层次越深,加载越慢,文件windows下的我没有尝试,有兴趣的可以试下。
那么路径查找到了,那文件扩展名定位呢?一般情况下,node会按照.js, .json, .node的次序依次查找,由于文件会调用同步阻塞式的判断文件是否存在,所以如果是json和node扩展名的文件,在require是将扩展名带上,这个可以更快的加载到文件。
那如果require('module_test')找到了一个module_test文件夹怎么办呢?node会在当前目录查找package.json通过parse这个文件,解析出要用的module,如果没有这个文件,就找index.js, index.json, index.node。
如果最后还没找到,那就会抛出个异常。