Node.js API详解之 module模块用法实例分析
本文实例讲述了Node.jsAPI详解之module模块用法。分享给大家供大家参考,具体如下:
Node.jsAPI详解之module
Node.js有一个简单的模块加载系统。
在Node.js中,文件和模块是一一对应的(每个文件被视为一个独立的模块)。
例子,假设有一个名为foo.js的文件:
constcircle=require('./circle.js'); console.log(`半径为4的圆的面积是${circle.area(4)}`);
在第一行中,foo.js加载了同一目录下的circle.js模块。
circle.js文件的内容为:
const{PI}=Math; exports.area=(r)=>PI*r**2; exports.circumference=(r)=>2*PI*r;
circle.js模块导出了area()和circumference()两个函数。
通过在特殊的exports对象上指定额外的属性,函数和对象可以被添加到模块的根部。
模块内的本地变量是私有的,因为模块被Node.js包装在一个函数中(详见模块包装器)。
在这个例子中,变量PI是circle.js私有的。
module.exports属性可以被赋予一个新的值(例如函数或对象)。
如下,bar.js会用到square模块,square导出一个构造函数:
constsquare=require('./square.js'); constmySquare=square(2); console.log(`正方形的面积是${mySquare.area()}`);
square模块定义在square.js中:
//赋值给`exports`不会修改模块,必须使用`module.exports` module.exports=(width)=>{ return{ area:()=>width**2 }; };
模块系统在require(‘module')模块中实现。
模块包装器
说明:
在执行模块代码之前,Node.js会使用一个如下的函数包装器将其包装:
(function(exports,require,module,__filename,__dirname){ //模块的代码实际上在这里 });
通过这样做,Node.js实现了以下几点:
它保持了顶层的变量(用var、const或let定义)作用在模块范围内,而不是全局对象。
它有助于提供一些看似全局的但实际上是模块特定的变量,例如:
实现者可以用于从模块中导出值的module和exports对象。
包含模块绝对文件名和目录路径的快捷变量__filename和__dirname。
文件模块
说明:
如果按确切的文件名没有找到模块,则Node.js会尝试带上.js、.json或.node拓展名再加载。
.js文件会被解析为JavaScript文本文件,
.json文件会被解析为JSON文本文件。
.node文件会被解析为通过dlopen加载的编译后的插件模块。
以‘/'为前缀的模块是文件的绝对路径。例如,require(‘/home/marco/foo.js')会加载/home/marco/foo.js文件。
以‘./'为前缀的模块是相对于调用require()的文件的。也就是说,circle.js必须和foo.js在同一目录下以便于require(‘./circle')找到它。
当没有以‘/'、'./'或‘../'开头来表示文件时,这个模块必须是一个核心模块或加载自node_modules目录。
如果给定的路径不存在,则require()会抛出一个code属性为‘MODULE_NOT_FOUND'的Error。
目录作为模块
说明:
可以把程序和库放到一个单独的目录,然后提供一个单一的入口来指向它。
把目录递给require()作为一个参数,有三种方式。
第一种方式是在根目录下创建一个package.json文件,并指定一个main模块。
例子,package.json文件类似:
{"name":"some-library", "main":"./lib/some-library.js"}
如果这是在./some-library目录中,则require(‘./some-library')会试图加载./some-library/lib/some-library.js。
这就是Node.js处理package.json文件的方式。
注意:如果package.json中“main”入口指定的文件不存在,则无法解析,Node.js会将模块视为不存在,并抛出默认错误:
Error:Cannotfindmodule'some-library'
如果目录里没有package.json文件,则Node.js就会试图加载目录下的index.js或index.node文件。
例如,如果上面的例子中没有package.json文件,则require(‘./some-library')会试图加载:
./some-library/index.js
./some-library/index.node
模块加载顺序
说明:
如果传递给require()的模块标识符不是一个核心模块,也没有以‘/'、‘../'或‘./'开头,
则Node.js会从当前模块的父目录开始,尝试从它的/node_modules目录里加载模块。
Node.js不会附加node_modules到一个已经以node_modules结尾的路径上。
如果还是没有找到,则移动到再上一层父目录,直到文件系统的根目录。
例子,如果在‘/home/ry/projects/foo.js'文件里调用了require(‘bar.js'),则Node.js会按以下顺序查找:
/home/ry/projects/node_modules/bar.js
/home/ry/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
通过在模块名后包含一个路径后缀,可以请求特定的文件或分布式的子模块。
例如,require(‘example-module/path/to/file')会把path/to/file解析成相对于example-module的位置。
后缀路径同样遵循模块的解析语法。
从全局目录加载
说明:
如果NODE_PATH环境变量被设为一个以冒号分割的绝对路径列表,则当在其他地方找不到模块时Node.js会搜索这些路径。
注意:在Windows系统中,NODE_PATH是以分号间隔的。
在当前的模块解析算法运行之前,NODE_PATH最初是创建来支持从不同路径加载模块的。
虽然NODE_PATH仍然被支持,但现在不太需要,因为Node.js生态系统已制定了一套存放依赖模块的约定。
有时当人们没意识到NODE_PATH必须被设置时,依赖NODE_PATH的部署会出现意料之外的行为。
有时一个模块的依赖会改变,导致在搜索NODE_PATH时加载了不同的版本(甚至不同的模块)。
此外,Node.js还会搜索以下位置:
1:$HOME/.node_modules
2:$HOME/.node_libraries
3:$PREFIX/lib/node
其中$HOME是用户的主目录,$PREFIX是Node.js里配置的node_prefix。
这些主要是历史原因。
注意:强烈建议将所有的依赖放在本地的node_modules目录。这样将会更快地加载,且更可靠。
缓存
说明:
模块在第一次加载后会被缓存。这也意味着(类似其他缓存机制)如果每次调用require(‘foo')都解析到同一文件,则返回相同的对象。
多次调用require(foo)不会导致模块的代码被执行多次。这是一个重要的特性。
借助它,可以返回“部分完成”的对象,从而允许加载依赖的依赖,即使它们会导致循环依赖。
如果想要多次执行一个模块,可以导出一个函数,然后调用该函数。
注意:
模块是基于其解析的文件名进行缓存的。由于调用模块的位置的不同,模块可能被解析成不同的文件名(比如从node_modules目录加载),
这样就不能保证require(‘foo')总能返回完全相同的对象。
此外,在不区分大小写的文件系统或操作系统中,被解析成不同的文件名可以指向同一文件,但缓存仍然会将它们视为不同的模块,并多次重新加载。
例如,require(‘./foo')和require(‘./FOO')返回两个不同的对象,而不会管./foo和./FOO是否是相同的文件。
循环
说明:
当循环调用require()时,一个模块可能在未完成执行时被返回。
例如以下情况:
a.js:
console.log('a开始'); exports.done=false; constb=require('./b.js'); console.log('在a中,b.done=%j',b.done); exports.done=true; console.log('a结束');
b.js:
console.log('b开始'); exports.done=false; consta=require('./a.js'); console.log('在b中,a.done=%j',a.done); exports.done=true; console.log('b结束');
main.js:
console.log('main开始'); consta=require('./a.js'); constb=require('./b.js'); console.log('在main中,a.done=%j,b.done=%j',a.done,b.done);
当main.js加载a.js时,a.js又加载b.js。
此时,b.js会尝试去加载a.js。
为了防止无限的循环,会返回一个a.js的exports对象的未完成的副本给b.js模块。
然后b.js完成加载,并将exports对象提供给a.js模块。
当main.js加载这两个模块时,它们都已经完成加载。因此,该程序的输出会是:
$nodemain.js
main开始
a开始
b开始
在b中,a.done=false
b结束
在a中,b.done=true
a结束
在main中,a.done=true,b.done=true
__dirname
说明:
当前模块的文件夹名称。等同于__filename的path.dirname()的值。
demo:
console.log(__dirname); ///Users/xiaoqiang/Documents/work/demo/NodeApi
__filename
说明:
当前模块的文件名称—解析后的绝对路径。
在主程序中这不一定要跟命令行中使用的名称一致。
demo:
console.log(__filename); ///Users/xiaoqiang/Documents/work/demo/NodeApi/app.js
module.exports
说明:
module.exports对象是由模块系统创建的。
有时这是难以接受的;许多人希望他们的模块成为某个类的实例。
为了实现这个,需要将期望导出的对象赋值给module.exports。
注意,将期望的对象赋值给exports会简单地重新绑定本地exports变量,这可能不是期望的。
注意,对module.exports的赋值必须立即完成。不能在任何回调中完成。
demo:
//a.js constEventEmitter=require('events'); module.exports=newEventEmitter(); //app.js consta=require('./a'); a.on('ready',()=>{ console.log('模块a已准备好'); });
exports
说明:
这是一个对于module.exports的更简短的引用形式。
exports变量是在模块的文件级别作用域内有效的,它在模块被执行前被赋予module.exports的值。
它有一个快捷方式,以便module.exports.f=…
可以被更简洁地写成exports.f=…。
注意,就像任何变量,如果一个新的值被赋值给exports,它就不再绑定到module.exports:
demo:
//a.js constEventEmitter=require('events'); exports.events=newEventEmitter(); //app.js const{events}=require('./a'); events.on('ready',()=>{ console.log('模块a已准备好'); });
require()
说明:
使用该方法引入模块。
demo:
const{events}=require('./a'); events.on('ready',()=>{ console.log('模块a已准备好'); });
require.main
说明:
当Node.js直接运行一个文件时,require.main会被设为它的module。
这意味着可以通过require.main===module来判断一个文件是否被直接运行:
对于foo.js文件,如果通过nodefoo.js运行则为true,但如果通过require(‘./foo')运行则为false。
因为module提供了一个filename属性(通常等同于__filename),
所以可以通过检查require.main.filename来获取当前应用程序的入口点。
demo:
constevents=require('./a'); console.log(require.main); //Module{ //id:'.', //exports:{}, //parent:null, //filename:'/Users/xiaoqiang/Documents/work/demo/NodeApi/app.js', //loaded:false, //children: //[Module{ //id:'/Users/xiaoqiang/Documents/work/demo/NodeApi/a.js', //exports:[Object], //parent:[Circular], //filename:'/Users/xiaoqiang/Documents/work/demo/NodeApi/a.js', //loaded:true, //children:[], //paths:[Array]}], //paths: //['/Users/xiaoqiang/Documents/work/demo/NodeApi/node_modules', //'/Users/xiaoqiang/Documents/work/demo/node_modules', //'/Users/xiaoqiang/Documents/work/node_modules', //'/Users/xiaoqiang/Documents/node_modules', //'/Users/xiaoqiang/node_modules', //'/Users/node_modules', //'/node_modules'] //}
require.cache
说明:
被引入的模块将被缓存在这个对象中。
从此对象中删除键值对将会导致下一次require重新加载被删除的模块。
注意不能删除nativeaddons(原生插件),因为它们的重载将会导致错误。
demo:
constevents=require('./a'); console.log(require.cache); //{'/Users/xiaoqiang/Documents/work/demo/NodeApi/app.js': //Module{ //id:'.', //exports:{}, //parent:null, //filename:'/Users/xiaoqiang/Documents/work/demo/NodeApi/app.js', //loaded:false, //children:[[Module]], //paths: //['/Users/xiaoqiang/Documents/work/demo/NodeApi/node_modules', //'/Users/xiaoqiang/Documents/work/demo/node_modules', //'/Users/xiaoqiang/Documents/work/node_modules', //'/Users/xiaoqiang/Documents/node_modules', //'/Users/xiaoqiang/node_modules', //'/Users/node_modules', //'/node_modules']}, //'/Users/xiaoqiang/Documents/work/demo/NodeApi/a.js': //Module{ //id:'/Users/xiaoqiang/Documents/work/demo/NodeApi/a.js', //exports:{events:[EventEmitter]}, //parent: //Module{ //id:'.', //exports:{}, //parent:null, //filename:'/Users/xiaoqiang/Documents/work/demo/NodeApi/app.js', //loaded:false, //children:[Array], //paths:[Array]}, //filename:'/Users/xiaoqiang/Documents/work/demo/NodeApi/a.js', //loaded:true, //children:[], //paths: //['/Users/xiaoqiang/Documents/work/demo/NodeApi/node_modules', //'/Users/xiaoqiang/Documents/work/demo/node_modules', //'/Users/xiaoqiang/Documents/work/node_modules', //'/Users/xiaoqiang/Documents/node_modules', //'/Users/xiaoqiang/node_modules', //'/Users/node_modules', //'/node_modules'] //} //}
require.extensions(已废弃)
说明:
指示require怎样处理特定的文件扩展名。
以前这被用来将非JavaScript模块按需编译后加载到Node.js中。
然而,在实践中,有更多更好的解决方案,比如用其它Node.js程序加载模块,或者提前将它们编译为JavaScript模块。
由于模块系统已锁定,这个特性可能永远不会消失,但是鉴于其复杂性和可能导致的小问题,最好不要碰它。
例如:把.sjs文件当做.js文件处理:
demo:
require.extensions['.sjs']=require.extensions['.js'];
require.resolve(request[,options])
说明:
使用内部的require()机制查询模块的位置,此操作只返回解析后的文件名,不会加载该模块。
request:需要解析的模块路径。
options.paths:解析模块的起点路径数组。此参数存在时,将使用这些路径而非默认解析路径。
demo:
constevents=require('./a'); console.log(require.resolve('./a')); ///Users/xiaoqiang/Documents/work/demo/NodeApi/a.js
require.resolve.paths(request)
说明:
返回一个数组,其中包含解析request过程中被查询的路径。
request:被查询解析路径的模块的路径。
demo:
constevents=require('./a'); console.log(require.resolve.paths('./a')); //['/Users/xiaoqiang/Documents/work/demo/NodeApi']
module
说明:
返回对当前模块的引用,是一个module对象。
demo:
constevents=require('./a'); console.log(module); //Module{ //id:'.', //exports:{}, //parent:null, //filename:'/Users/xiaoqiang/Documents/work/demo/NodeApi/app.js', //loaded:false, //children: //[Module{ //id:'/Users/xiaoqiang/Documents/work/demo/NodeApi/a.js', //exports:[Object], //parent:[Circular], //filename:'/Users/xiaoqiang/Documents/work/demo/NodeApi/a.js', //loaded:true, //children:[], //paths:[Array]}], //paths: //['/Users/xiaoqiang/Documents/work/demo/NodeApi/node_modules', //'/Users/xiaoqiang/Documents/work/demo/node_modules', //'/Users/xiaoqiang/Documents/work/node_modules', //'/Users/xiaoqiang/Documents/node_modules', //'/Users/xiaoqiang/node_modules', //'/Users/node_modules', //'/node_modules'] //}
module对象
说明:
在每个模块中,module的自由变量是一个指向表示当前模块的对象的引用。
为了方便,module.exports也可以通过全局模块的exports对象访问。
module实际上不是全局的,而是每个模块本地的。
module.id
说明:
返回模块的标识符。通常是完全解析后的文件名。
demo:
constevents=require('events'); console.log(module.id); //.
module.parent
说明:
最先引用该模块的模块。
demo:
constevents=require('events'); console.log(module.parent); //null
module.children
说明:
返回被该模块引用的模块对象。
demo:
constevents=require('events'); console.log(module.children); //[]
module.filename
说明:
返回模块的完全解析后的文件名。
demo:
constevents=require('events'); console.log(module.filename); ///Users/xiaoqiang/Documents/work/demo/NodeApi/app.js
module.loaded
说明:
返回模块是否已经加载完成,或正在加载中。
demo:
constevents=require('events'); console.log(module.loaded); //false
module.paths
说明:
返回模块的搜索路径。
demo:
constevents=require('events'); console.log(module.paths); //['/Users/xiaoqiang/Documents/work/demo/NodeApi/node_modules', //'/Users/xiaoqiang/Documents/work/demo/node_modules', //'/Users/xiaoqiang/Documents/work/node_modules', //'/Users/xiaoqiang/Documents/node_modules', //'/Users/xiaoqiang/node_modules', //'/Users/node_modules', //'/node_modules']
希望本文所述对大家node.js程序设计有所帮助。