在分析标识符的过程中,require()通过分析文件扩展名之后,可能没有查找到对应文件,但却得到一个目录,这在引入自定义模块和逐个模块路径进行查找时经常会出现,此时Node会将目录当做一个包来处理
在这个过程中,Node对CommonJS包规范进行了一定程度的支持。首先,Node在当前目录下查找package.json(CommonJS包规范定义的包描述文件),通过JSON.parse()解析出包描述对象,从中取出main属性指定的文件名进行定位。如果文件名缺少扩展名,将会进入扩展名分析的步骤
而如果main属性指定的文件名错误,或者压根没有package.json文件,Node会将index当做默认文件名,然后依次查找index.js、index.json、index.node
如果在目录分析的过程中没有定位成功任何文件,则自定义模块进入下一个模块路径进行查找。如果模块路径数组都被遍历完毕,依然没有查找到目标文件,则会抛出查找失败的异常
访问变量
如何在一个模块中访问另外一个模块中定义的变量呢?
【global】
最容易想到的方法,把一个模块定义的变量复制到全局环境global中,然后另一个模块访问全局环境即可
//a.js var a = 100; global.a = a; //b.js require('./a'); console.log(global.a);//100
这种方法虽然简单,但由于会污染全局环境,不推荐使用
【module】
而常用的方法是使用nodejs提供的模块对象Module,该对象保存了当前模块相关的一些信息
function Module(id, parent) { this.id = id; this.exports = {}; this.parent = parent; if (parent && parent.children) { parent.children.push(this); } this.filename = null; this.loaded = false; this.children = []; }
module.id 模块的识别符,通常是带有绝对路径的模块文件名。
module.filename 模块的文件名,带有绝对路径。
module.loaded 返回一个布尔值,表示模块是否已经完成加载。
module.parent 返回一个对象,表示调用该模块的模块。
module.children 返回一个数组,表示该模块要用到的其他模块。
module.exports 表示模块对外输出的值。
【exports】
module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量
//a.js var a = 100; module.exports.a = a; //b.js var result = require('./a'); console.log(result);//'{ a: 100 }'
为了方便,Node为每个模块提供一个exports变量,指向module.exports。造成的结果是,在对外输出模块接口时,可以向exports对象添加方法
console.log(module.exports === exports);//true
[注意]不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系
模块编译
编译和执行是模块实现的最后一个阶段。定位到具体的文件后,Node会新建一个模块对象,然后根据路径载入并编译。对于不同的文件扩展名,其载入方法也有所不同,具体如下所示
js文件——通过fs模块同步读取文件后编译执行
node文件——这是用C/C++编写的扩展文件,通过dlopen()方法加载最后编译生成的文件
json文件——通过fs模块同步读取文件后,用JSON.parse()解析返回结果
其余扩展名文件——它们都被当做.js文件载入
每一个编译成功的模块都会将其文件路径作为索引缓存在Module._cache对象上,以提高二次引入的性能
根据不同的文件扩展名,Node会调用不同的读取方式,如.json文件的调用如下:
// Native extension for .json Module._extensions['.json'] = function(module, filename) { var content = NativeModule.require('fs').readFileSync(filename, 'utf8'); try { module.exports = JSON.parse(stripBOM(content)); } catch (err) { err.message = filename + ': ' + err.message; throw err; } };
其中,Module._extensions会被赋值给require()的extensions属性,所以通过在代码中访问require.extensions可以知道系统中已有的扩展加载方式。编写如下代码测试一下:
console.log(require.extensions);
得到的执行结果如下:
{ '.js': [Function], '.json': [Function], '.node': [Function] }
在确定文件的扩展名之后,Node将调用具体的编译方式来将文件执行后返回给调用者
【JavaScript模块的编译】