手机版

node.js的解释需要()源代码

时间:2021-09-20 来源:互联网 编辑:宝哥软件园 浏览:

2009年,Node.js项目诞生,所有模块都是CommonJS格式。

截至目前,npmjs.com node . js的模块仓库已经存储了15万个模块,大部分都是CommonJS格式。

这种格式的核心是require语句,通过该语句加载模块。要学习Node.js,您必须学习如何使用require语句。通过源代码分析,详细介绍了require语句的内部运行机制,帮助大家理解Node.js的模块机制

一、require()的基本用法。

在分析源代码之前,先介绍一下require语句的内部逻辑。如果你只想知道require的用法,就看看这一段。

以下内容翻译自《Node使用手册》。

复制的代码如下:当节点遇到require(X)时,将按照以下顺序进行处理。(1)如果x是内置模块(如require ('http ')),则a .返回该模块。没有进一步的实施。(2)如果x以'开头。/'或'/'或'./',a .根据x的父模块确定x的绝对路径。b .以x为文件,依次搜索以下文件,只要其中一个文件存在,就返回该文件,停止执行。X. js x. JSON x. node C .把x看作一个目录,依次查找下面的文件,如果其中有一个文件存在,则返回该文件,停止执行。X/package.json(主字段)x/index . jsx/index . JSON x/index . node(3)如果x没有路径a .根据x所在的父模块,确定x. B可能的安装目录,在每个目录中依次加载x作为文件名或目录名。(4)抛出“未找到”。

请看一个例子。

当前的脚本文件/home/ry/projects/foo.js实现了require('bar '),属于上面的第三种情况。Node的内部操作流程如下。

首先,确定x的绝对路径可能是以下位置,依次搜索每个目录。

复制的代码如下:/home/ry/project/node _ modules/bar/home/ry/node _ modules/bar/home/node _ modules/bar/node _ modules/bar。

搜索时,Node首先将bar作为文件名,然后依次尝试加载以下文件,如果成功则返回。

barba . jsbar . JSON bar . node如果没有一个成功,说明bar可能是目录名,所以尽量依次加载以下文件。

复制了以下代码:bar/package.json(主字段)bar/index . jsbar/index . JSON bar/index . node。

如果在所有目录中找不到bar对应的文件或目录,则会引发错误。

第二,模块构造器。

了解了内部逻辑之后,我们再来看看源代码。

所需的源代码在Node的lib/module.js文件中。为了便于理解,本文引用的源代码进行了简化,删除了原作者的评论。

函数模块(id,parent){ this . id=id;this . exports={ };this.parent=parentthis.filename=nullthis.loaded=falsethis . children=[];}模块。导出=模块;var模块=新模块(文件名,父模块);在上面的代码中,Node定义了一个构造器Module,所有Modules都是该模块的实例。可以看到,当前的Module (module.js)也是Module的一个实例。

每个实例都有自己的属性。让我们举个例子来看看这些属性的值是什么。创建新的脚本文件a.js

//a . js console . log(' module . id : ',module . id);console . log(' module . exports 3360 ',module . exports);console . log(' module . parent : ',module . parent);console . log(' module . filename : ',module . filename);console . log(' module . loaded : ',module . loaded);console . log(' module . children : ',module . children);console . log(' module . path : ',module.paths运行这个脚本。

$ node a . js模块。id :模块。导出: { }模块。父: nullmodule。filename :/home/ruanyf/tmp/a . js模块。加载了: falsmodule。儿童3360[]模块。路径3360['/home/ruanyf/tmp/node _ modules ','/home/ruanyf/node_modules ','/home/node_modules ','/node_modules' ]可以看到,如果没有父模块,直接调用当前模块,家长属性就是空,id属性就是一个点文件名属性是模块的绝对路径,路径属性是一个数组,包含了模块可能的位置。另外,输出这些内容时,模块还没有全部加载,所以加载属性为假的。

新建另一个脚本文件b.js,让其调用a.js。

//b.jsvar a=require(' ./a . js ');运行b.js。

$ node b . js模块。id :/home/ruanyf/tmp/a . js模块。导出s : { }模块。父: {对象}模块。filename :/home/ruanyf/tmp/a . js模块。加载了:儿童3360[]模块。路径s :['/home/ruanyf/tmp/node _ modules ','/home/ruanyf/node_modules ','/home上面代码中,由于a.js被b.js调用,所以父母属性指向b.js模块,id属性和文件名属性一致,都是模块的绝对路径。

三、模块实例的需要方法

每个模块实例都有一个需要方法。

模块。原型。require=function(路径){返回模块._加载(路径,这个);};由此可知,要求并不是全局性命令,而是每个模块提供的一个内部方法,也就是说,只有在模块内部才能使用需要命令(唯一的例外是取代环境)。另外,要求其实内部调用模块。_加载方法。

下面来看模块。_加载的源码。

模块_load=函数(请求,父,isMain) { //计算绝对路径变量文件名=模块_resolveFilename(请求,父级);//第一步:如果有缓存,取出缓存变量缓存模块=模块。_缓存[文件名];if(缓存模块){返回缓存模块。出口;//第二步:是否为内置模块if (NativeModule.exists(文件名)){ return NativeModule.require(文件名);} //第三步:生成模块实例,存入缓存定义变量模块=新模块(文件名,父模块);模块_缓存[文件名]=模块;//第四步:加载模块请尝试{ module.load(文件名);hadException=false}最后{ if(HadException)}删除模块。_缓存[文件名];} } //第五步:输出模块的出口属性返回模块。导出;};上面代码中,首先解析出模块的绝对路径(文件名),以它作为模块的识别符。然后,如果模块已经在缓存中,就从缓存取出;如果不在缓存中,就加载模块。

因此,模块。_加载的关键步骤是两个。

复制代码代码如下:Module._resolveFilename():确定模块的绝对路径module.load():加载模块

四、模块的绝对路径

下面是模块_resolveFilename .方法的源码。

模块_resolveFilename=函数(请求,父){ //第一步:如果是内置模块,不含路径返回if(nativemodule。存在(请求)){返回请求;} //第二步:确定所有可能的路径var resolvedModule=模块_ resolveLookupPaths(请求,父级);var id=解析的模块[0];var path=解析的模块[1];//第三步:确定哪一个路径为真变量文件名=模块_findPath(请求,路径);if(!文件名){ var err=新错误('找不到模块''请求' ');err.code=' MODULE _ NOT _ FOUND抛出错误;}返回文件名;};上面代码中,在Module.resolveFilename方法内部,又调用了两个方法Module.resolveLookupPaths()和模块_findPath(),前者用来列出可能的路径,后者用来确认哪一个路径为真。

为了简洁起见,这里只给出模块_resolveLookupPaths()的运行结果。

复制代码代码如下:['/home/ruanyf/tmp/node _ modules ','/home/ruanyf/node_modules ','/home/node_modules ','/node_modules' '/home/ruanyf/.node_modules ','/home/ruanyf/.node_libraries ',' $Prefix/lib/node' ]

上面的数组是模块的所有可能路径。基本上,从当前路径开始,逐级查找node_modules子目录。后三种路径,主要是出于历史原因,仍然兼容,但实际上很少使用。

有了可能的路径后,下面是Module的源代码。_findPath(),用于确定哪个是正确的路径。

模块。_ findpath=function (request,path){//列出所有可能的后缀:js,JSON,node varexts=object.keys(模块。_ extensions);//如果是绝对路径,不会搜索if (request。charat(0)==='/'){ path=[' '];}//是否有后缀目录斜杠var尾随斜杠=(request . slice(-1)=='/');//第一步:如果当前路径已经在缓存中,直接返回缓存varcachekey=JSON . stringfy({ request 3360 request,path s 3360 path });如果(模块。_路径缓存[缓存键]) {返回模块。_ Pathcache[cacheKey];}//第二步:遍历(var i=0,PL=paths.length一、PL;I){ var base path=path . resolve(path[I],request);var文件名;if(!TrailingSlash) {//第三步:模块文件文件名=tryFile(basePath)是否存在;if(!文件名!TrailingSlash) {//第四步:给模块文件添加后缀名称,以及filename=try extensions(base path,exts)是否存在;} }//第五步:有没有package.json if(!filename){ filename=tryPackage(base path,exts);} if(!文件名){//第六步:是否有后缀filename=tryextensions(路径。目录名的解析(basepath、' index ')、exts);}//第七步:将找到的文件路径存储在返回缓存中,然后返回if (filename) {module。_ pathcache [cachekey]=文件名;返回文件名;} }//步骤8:没有找到文件,返回false返回false;};经过上面的代码,可以找到模块的绝对路径。

有时候在项目代码中,需要调用模块的绝对路径,所以除了module.filename之外,Node还提供了一个require.resolve方法进行外部调用,用来从模块名称中获取绝对路径。

require.resolve=函数(请求){返回模块。_resolveFilename(请求,自身);};//用法require.resolve('a.js')//返回/home/ruanyf/tmp/a.js v .加载模块。

有了模块的绝对路径,就可以加载模块了。下面是module.load方法的源代码。

module . prototype . load=function(filename){ var extension=path . extname(filename)| | '。js ';if(!模块。_ extensions[extension])extension='。js ';模块。_扩展名[扩展名](this,filename);this.loaded=true};在上面的代码中,首先确定模块的后缀名称,不同的后缀名称对应不同的加载方式。以下是后缀对应的处理方法。js和。json。

模块。_扩展名['。js']=函数(模块,文件名){ var content=fs.readFileSync(文件名,‘utf8’);模块。_编译(stripBOM(内容),文件名);};模块。_扩展名['。json']=函数(模块,文件名){ var content=fs.readFileSync(文件名,‘utf8’);try { module . exports=JSON . parse(strip BoM(content));} catch(err){ err . message=filename ' : ' err . message;抛出错误;}};这里只讨论js文件的加载。首先以字符串形式读取模块文件,然后剥离utf8编码独有的BOM头,最后编译模块。

模块。_compile方法用于编译模块。

module . prototype . _ compile=function(content,filename){ var self=this;var args=[self.exports,require,self,filename,dirname];返回Compiledwrapper . apply(self . exports,args);};上面的代码基本相当于下面的形式。

(函数(exports,require,module,_ _ filename,_ _ dirname) {//module源代码});也就是说,加载一个模块本质上意味着注入三个全局变量,export、require和module,然后执行模块的源代码,然后输出模块的exports变量值。

(结束)

版权声明:node.js的解释需要()源代码是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。