当前位置: 首页 > 后端技术 > Node.js

需要源码阅读

时间:2023-04-04 00:25:37 Node.js

requiregitlabmuduleobjectificationrequire最终会将每个模块转化为对象functionModule(id,parent){this.id=id;this.exports={};this.parent=parent;updateChildren(父母,这个,假);this.filename=null;this.loaded=false;this.children=[];}require方法使用assert断言输入的有效性并调用_load方法。还有一个模块调用_load.runMain=function(){//加载主模块——命令行参数。Module._load(process.argv[1],null,true);//处理在程序进程的第一个tick中添加的任何nextTicks._tickCallback();};这个我不是特别确定,但是我基本确定是nodexxx.js命令调用的load方法。这里有很多关于处理main的东西,核心部分是if(isMain){process.mainModule=module;module.id='.';}这证明了上面的说法,runMain是为nodexxx.js命令调用的。另外,主模块的id必须是'.',parent必须为空。此外,它会在调用前检查模块缓存是否存在。为了简化删除主模块的处理,下面的代码创建了一个没有原型的空对象Module._cache=Object.create(null);Module._pathCache=Object.create(null);Module._load=function(request,parent,isMain){if(parent){debug('Module._loadREQUEST%sparent:%s',request,parent.id);}varfilename=Module._resolveFilename(request,parent,isMain);//缓存中是否存在varcachedModule=Module._cache[filename];如果(cachedModule){updateChildren(parent,cachedModule,true);返回cachedModule.exports;}//是否是原生模块if(NativeModule.nonInternalExists(filename)){debug('loadnativemodule%s',request);返回NativeModule.require(文件名);}//其他模块处理varmodule=newModule(filename,parent);Module._cache[文件名]=模块;tryModuleLoad(模块,文件名);返回module.exports;};这部分说明加载一个模块首先是判断模块名,然后是找缓存,找到native模块,然后是其他模块。最后一个return是最关键的,返回值永远是模块的exportsfornode_modulesfolderlookupRulesModule._nodeModulePaths=function(from){//保证'from'是绝对的.from=path.resolve(从);//尽早返回不仅是为了避免不必要的工作,也是为了*避免*返回//根的两个项目的数组:['//node_modules','/node_modules']if(from==='/')返回['/node_modules'];//注意:此方法*仅*在保证路径为绝对路径时有效。做一个在Windows和Posix上都适用的完全边缘大小写正确的path.split//是非常重要的。常量路径=[];变量p=0;变种最后=从。长度;for(vari=from.length-1;i>=0;--i){constcode=from.charCodeAt(i);if(code===47//*/*/){if(p!==nmLen)paths.push(from.slice(0,last)+'/node_modules');最后=我;p=0;}elseif(p!==-1){if(nmChars[p]===代码){++p;}否则{p=-1;从from开始,逐层查找node_modules文件夹Module._extensionsjs,json,node,mjs每个后缀文件都有对应的打开方式js清除可能的BOM头并加载jsonjsonParsenode.node这是一个用C/C++编写的扩展文件。通过dlopen()方法加载最终编译好的文件,可以看成是系统调用findPath。这部分代码比较多,随便看一个评论。//给定模块名称和要测试的路径列表,返回以下优先级中的第一个//匹配文件。////require("a.")//->a.////require("a")//->a//->a.//->a/index.package.jsonnode将在路径中查找package.json文件并在里面加载main,并放入packagecache中,使用main中指定的文件确定绝对路径,然后加载functionreadPackage(requestPath){constentry=packageMainCache[requestPath];如果(条目)返回条目;constjsonPath=path.resolve(requestPath,'package.json');constjson=internalModuleReadFile(path.toNamespacedPath(jsonPath));如果(json===未定义){返回false;}try{varpkg=packageMainCache[requestPath]=JSON.parse(json).main;}catch(e){e.path=jsonPath;e.message='解析错误'+jsonPath+':'+e.message;扔e;}returnpkg;}compile文件怎么找就基本清楚了,接下来就是最常用的js的编译js模块外层必须覆盖Module.wrapper=['(function(exports,require,module,__filename,__dirname){','\n});'];之后就涉及到断点的处理,也就是在vm模块中的Script对象runInThisContext后面有一句话,解释了为什么node中的所有文件也可以有Modulerequire的各种方法和特性,包括那些文件不是主要的。此外,所有模块也是公共模块缓存。在require中使用ModulereturnModule._load(path,this,/*isMain*/false);使用你自己的this作为Module对象的父对象require=internalModule.makeRequireFunction(this);makeRequireFunction的代码//使用makeRequireFunction(module)调用,其中|module|是Module对象//用作require()函数的上下文。functionmakeRequireFunction(mod){constModule=mod.constructor;函数要求(路径){尝试{exports.requireDepth+=1;返回mod.require(path);}最后{exports.requireDepth-=1;}}functionresolve(request,options){returnModule._resolveFilename(request,mod,false,options);}require.resolve=resolve;函数路径(请求){返回Module._resolveLookup路径(请求、模组、真);}resolve.paths=路径;require.main=process.mainModule;//启用支持以添加额外的扩展类型。require.extensions=Module._extensions;require.cache=Module._cache;returnrequire;}执行compiledWrapper,对应wrapper插入的jsresult=compiledWrapper.call(this.exports,this.exports,require,this,filename,dirname);首先,compiledWrapper的this绑定了Module自己的exports,自己的exports也作为参数注入,相当于隐式赋值。exports是compiledWrapper中的导出。这成为唯一的外部输出。所有其他值对compiledWrapper来说都是私有的,不会污染整个世界。require用作参数。注入,另一种是文件名和路径的注入。从上面的加载过程我们可以发现,只要有require节点,就会一直加载下去。另外,这里也体现了CommonJs同步的特点。